Fazer backup de pares de chave-valor usando o Android Backup Service

Mantenha tudo organizado com as coleções Salve e categorize o conteúdo com base nas suas preferências.

O Android Backup Service oferece backup e restauração com armazenamento em nuvem para dados de chave-valor no seu app Android. Durante esse tipo de operação, os dados de backup do app são transmitidos para a transferência de backup do dispositivo. Se o dispositivo estiver usando a Transferência de backup padrão do Google, os dados serão transferidos para o Android Backup Service para arquivamento.

Os dados são limitados a 5 MB por usuário do app. Não há cobrança pelo armazenamento dos dados de backup.

Para ver uma visão geral das opções de backup do Android e uma orientação sobre quais dados você precisa armazenar em backup e restaurar, consulte a Visão geral do backup de dados.

Implementar backup de chave-valor

Para fazer backup dos dados do app, é necessário implementar um agente de backup. Seu agente de backup é chamado pelo Backup Manager durante o backup e a restauração.

Para implementar um agente de backup, é necessário:

  1. Declarar o agente de backup no arquivo do manifesto usando o atributo android:backupAgent.

  2. Definir um agente de backup realizando uma ou mais das ações a seguir:

    • Estender o BackupAgent

      A classe BackupAgent fornece a interface central usada pelo app para se comunicar com o Backup Manager. Se você estender essa classe diretamente, será necessário substituir os métodos onBackup() e onRestore() para processar as operações de backup e restauração dos dados.

    • Estender o BackupAgentHelper

      A classe BackupAgentHelper fornece um wrapper conveniente para a classe BackupAgent, o que diminui a quantidade de código necessária. No BackupAgentHelper, é necessário usar um ou mais objetos auxiliares, que fazem o backup e a restauração de determinados tipos de dados automaticamente. Assim, você não precisará implementar onBackup() e onRestore() A menos que você precise ter controle total dos backups do app, recomendamos o uso do BackupAgentHelper para processá-los.

      Atualmente, o Android oferece auxiliares de backup que farão backup e restauração de arquivos completos das SharedPreferences e do armazenamento interno.

Declarar o agente de backup no manifesto

Depois de decidir o nome da classe do agente de backup, declare-o no manifesto usando o atributo android:backupAgent na tag <application>.

Exemplo:

<manifest ... >
    ...
    <application android:label="MyApplication"
                 android:backupAgent="MyBackupAgent">
        <meta-data android:name="com.google.android.backup.api_key"
            android:value="unused" />
        <activity ... >
            ...
        </activity>
    </application>
</manifest>

Para oferecer compatibilidade com dispositivos mais antigos, recomendamos adicionar a chave de API <meta-data> ao arquivo de manifesto do Android. O Android Backup Service não exige mais uma chave de serviço, mas alguns dispositivos mais antigos ainda podem verificar a existência de uma chave ao fazer backup. Defina android:name como com.google.android.backup.api_key e android:value como unused.

O atributo android:restoreAnyVersion usa um valor booleano para indicar se você quer restaurar os dados do app, independentemente da versão atual dele em comparação com a versão que produziu os dados de backup. O valor padrão é false. Consulte o guia Verificar a versão dos dados de restauração para mais informações.

Estender o BackupAgentHelper

Crie seu agente de backup usando o BackupAgentHelper se quiser fazer backup de arquivos completos das SharedPreferences ou do armazenamento interno. A criação do agente de backup usando o BackupAgentHelper exige muito menos código do que a extensão do BackupAgent, porque não é necessário implementar os métodos onBackup() e onRestore().

Sua implementação do BackupAgentHelper precisa usar um ou mais auxiliares de backup. Um auxiliar de backup é um componente especializado que o BackupAgentHelper invoca para realizar operações de backup e restauração para um determinado tipo de dado. Atualmente, o framework do Android oferece dois auxiliares diferentes:

É possível incluir vários auxiliares no BackupAgentHelper, mas apenas um auxiliar é necessário para cada tipo de dado. Ou seja, se você tem vários arquivos SharedPreferences, apenas um SharedPreferencesBackupHelper é necessário.

Para cada auxiliar que você quiser adicionar ao BackupAgentHelper, é preciso fazer o seguinte durante o método onCreate():

  1. Criar uma instância da classe auxiliar que você quer usar. No construtor da classe, é preciso especificar quais arquivos que você quer fazer backup.
  2. Chamar o método addHelper(), para adicionar o auxiliar ao BackupAgentHelper.

As seções a seguir descrevem como criar um agente de backup usando cada auxiliar disponível.

Fazer backup de SharedPreferences

Ao instanciar um SharedPreferencesBackupHelper, é necessário incluir o nome de um ou mais arquivos SharedPreferences.

Por exemplo, para fazer backup de um arquivo SharedPreferences com o nome user_preferences, um agente de backup completo que usa o BackupAgentHelper ficará assim:

Kotlin

// The name of the SharedPreferences file
const val PREFS = "user_preferences"

// A key to uniquely identify the set of backup data
const val PREFS_BACKUP_KEY = "prefs"

class MyPrefsBackupAgent : BackupAgentHelper() {
    override fun onCreate() {
        // Allocate a helper and add it to the backup agent
        SharedPreferencesBackupHelper(this, PREFS).also {
            addHelper(PREFS_BACKUP_KEY, it)
        }
    }
}

Java

public class MyPrefsBackupAgent extends BackupAgentHelper {
    // The name of the SharedPreferences file
    static final String PREFS = "user_preferences";

    // A key to uniquely identify the set of backup data
    static final String PREFS_BACKUP_KEY = "prefs";

    // Allocate a helper and add it to the backup agent
    @Override
    public void onCreate() {
        SharedPreferencesBackupHelper helper =
                new SharedPreferencesBackupHelper(this, PREFS);
        addHelper(PREFS_BACKUP_KEY, helper);
    }
}

O SharedPreferencesBackupHelper inclui todo o código necessário para fazer o backup e a restauração de um arquivo de SharedPreferences.

Quando o Backup Manager chama os métodos onBackup() e onRestore(), o BackupAgentHelper chama os auxiliares de backup para fazer o backup e a restauração dos arquivos especificados.

Fazer backup de outros arquivos

Ao instanciar um FileBackupHelper, é necessário incluir o nome de um ou mais arquivos salvos no armazenamento interno do app, conforme especificado no método getFilesDir(), que é o mesmo local em que o método openFileOutput() grava arquivos.

Por exemplo, para fazer backup de dois arquivos com os nomes scores e stats, um agente de backup usando o BackupAgentHelper ficará assim:

Kotlin

// The name of the file
const val TOP_SCORES = "scores"
const val PLAYER_STATS = "stats"
// A key to uniquely identify the set of backup data
const val FILES_BACKUP_KEY = "myfiles"

class MyFileBackupAgent : BackupAgentHelper() {
    override fun onCreate() {
        // Allocate a helper and add it to the backup agent
        FileBackupHelper(this, TOP_SCORES, PLAYER_STATS).also {
            addHelper(FILES_BACKUP_KEY, it)
        }
    }
}

Java

public class MyFileBackupAgent extends BackupAgentHelper {
    // The name of the file
    static final String TOP_SCORES = "scores";
    static final String PLAYER_STATS = "stats";

    // A key to uniquely identify the set of backup data
    static final String FILES_BACKUP_KEY = "myfiles";

    // Allocate a helper and add it to the backup agent
    @Override
    public void onCreate() {
        FileBackupHelper helper = new FileBackupHelper(this,
                TOP_SCORES, PLAYER_STATS);
        addHelper(FILES_BACKUP_KEY, helper);
    }
}

O FileBackupHelper inclui todo o código necessário para fazer o backup e a restauração de arquivos salvos no armazenamento interno do app.

No entanto, a leitura e a gravação de arquivos no armazenamento interno não são seguras para linhas de execução. Para garantir que o agente de backup não leia ou grave arquivos ao mesmo tempo que as atividades, é necessário usar instruções sincronizadas todas as vezes que você fizer uma leitura ou gravação. Por exemplo, em qualquer atividade em que você lê e grava o arquivo, é preciso usar um objeto como o bloqueio intrínseco das instruções sincronizadas:

Kotlin

// Object for intrinsic lock
companion object {
    val sDataLock = Any()
}

Java

// Object for intrinsic lock
static final Object sDataLock = new Object();

Depois, crie uma instrução sincronizada usando esse bloqueio sempre que ler ou gravar os arquivos. Por exemplo, veja uma instrução sincronizada para gravar a pontuação mais recente de um jogo em um arquivo:

Kotlin

try {
    synchronized(MyActivity.sDataLock) {
        val dataFile = File(filesDir, TOP_SCORES)
        RandomAccessFile(dataFile, "rw").apply {
            writeInt(score)
        }
    }
} catch (e: IOException) {
    Log.e(TAG, "Unable to write to file")
}

Java

try {
    synchronized (MyActivity.sDataLock) {
        File dataFile = new File(getFilesDir(), TOP_SCORES);
        RandomAccessFile raFile = new RandomAccessFile(dataFile, "rw");
        raFile.writeInt(score);
    }
} catch (IOException e) {
    Log.e(TAG, "Unable to write to file");
}

Sincronize suas instruções de leitura com o mesmo bloqueio.

Em seguida, no BackupAgentHelper, é preciso substituir os métodos onBackup() e onRestore() para sincronizar as operações de backup e restauração usando o mesmo bloqueio intrínseco. Por exemplo, o caso com o MyFileBackupAgent acima precisa destes métodos:

Kotlin

@Throws(IOException::class)
override fun onBackup(
        oldState: ParcelFileDescriptor,
        data: BackupDataOutput,
        newState: ParcelFileDescriptor
) {
    // Hold the lock while the FileBackupHelper performs back up
    synchronized(MyActivity.sDataLock) {
        super.onBackup(oldState, data, newState)
    }
}

@Throws(IOException::class)
override fun onRestore(
        data: BackupDataInput,
        appVersionCode: Int,
        newState: ParcelFileDescriptor
) {
    // Hold the lock while the FileBackupHelper restores the file
    synchronized(MyActivity.sDataLock) {
        super.onRestore(data, appVersionCode, newState)
    }
}

Java

@Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
          ParcelFileDescriptor newState) throws IOException {
    // Hold the lock while the FileBackupHelper performs back up
    synchronized (MyActivity.sDataLock) {
        super.onBackup(oldState, data, newState);
    }
}

@Override
public void onRestore(BackupDataInput data, int appVersionCode,
        ParcelFileDescriptor newState) throws IOException {
    // Hold the lock while the FileBackupHelper restores the file
    synchronized (MyActivity.sDataLock) {
        super.onRestore(data, appVersionCode, newState);
    }
}

Estender o BackupAgent

A maior parte dos apps não precisa estender a classe BackupAgent diretamente, mas é necessário estender o BackupAgentHelper para aproveitar as classes auxiliares integradas que fazem o backup e a restauração dos arquivos automaticamente. No entanto, você pode estender BackupAgent diretamente para:

  • Controlar as versões do seu formato de dados. Por exemplo, se você previr a necessidade de revisar o formato em que os dados do app são gravados, você pode criar um agente de backup para fazer uma verificação cruzada da versão do app durante uma operação de restauração. Faça todo o trabalho de compatibilidade necessário caso a versão no dispositivo seja diferente da versão dos dados de backup. Para ver mais informações, consulte Verificar a versão dos dados de restauração.
  • Especificar as partes dos dados para backup. Em vez de fazer backup de um arquivo inteiro, é possível especificar as partes dos dados que serão armazenadas em backup e como cada parte será restaurada no dispositivo. Isso também pode ajudar a gerenciar versões diferentes, porque os dados são lidos e gravados como entidades únicas, em vez de arquivos completos.
  • Fazer backup de dados em um banco de dados. Se você quiser restaurar um banco de dados SQLite quando o usuário reinstalar o app, será necessário criar um BackupAgent personalizado que leia os dados corretos durante uma operação de backup. Em seguida, crie uma tabela e insira os dados durante a operação de restauração.

Se você não precisa realizar nenhuma das tarefas acima e quer fazer backup dos arquivos completos de SharedPreferences ou do armazenamento interno, consulte Como estender o BackupAgentHelper.

Métodos obrigatórios

Ao criar um BackupAgent, é necessário implementar os seguintes métodos de callback:

onBackup()
O Backup Manager chama esse método depois que você solicita um backup. Nesse método, os dados do app são lidos do dispositivo, e os dados que você quer armazenar em backup são transmitidos para o Backup Manager, conforme descrito em Fazer um backup.
onRestore()

O Backup Manager chama esse método durante uma operação de restauração. Esse método exibe os dados de backup, que podem ser usados pelo app para restaurar o estado anterior, conforme descrito em Fazer uma restauração.

O sistema chama esse método para restaurar os dados de backup quando o usuário reinstalar o app, mas o app também poderá solicitar uma restauração.

Fazer um backup

Uma solicitação de backup não resulta em uma chamada imediata para o método onBackup(). Em vez disso, o Backup Manager aguarda o momento adequado e faz o backup de todos os apps que fizeram essa solicitação desde a data do último backup. Nesse momento, você precisará informar os dados do app ao Backup Manager para salvá-los no armazenamento em nuvem.

Só o Backup Manager pode chamar o método onBackup() do agente de backup. Sempre que os dados do app mudam e você quer fazer um backup, é necessário solicitar uma operação de backup chamando dataChanged(). Consulte Solicitar um backup para mais informações.

Dica: durante o desenvolvimento do app, é possível iniciar uma operação de backup imediata usando o Backup Manager com a ferramenta bmgr.

Quando o Backup Manager chama o método onBackup(), ele transmite três parâmetros:

oldState
Um ParcelFileDescriptor aberto somente leitura, que aponta para o último estado de backup fornecido pelo app. Esses não são os dados de backup do armazenamento em nuvem, mas uma representação local dos dados que foram armazenados na última vez em que o método onBackup() foi chamado, conforme definido por newState ou usando onRestore() O método onRestore() será abordado na próxima seção. Como o método onBackup() não autoriza a leitura de dados de backup existentes no armazenamento em nuvem, é possível usar essa representação local para determinar se os dados mudaram desde o último backup.
data
Um objeto BackupDataOutput, usado para enviar os dados de backup para o Backup Manager.
newState
Um ParcelFileDescriptor aberto de leitura/gravação, que aponta para um arquivo em que você precisa gravar uma representação dos dados enviados ao data. Uma representação pode ser apenas o carimbo de data/hora da última modificação do arquivo. Esse objeto será retornado como o oldState na próxima vez que o Backup Manager chamar o método onBackup(). Se você não gravar os dados de backup no newState, o oldState apontará um arquivo vazio na próxima vez que o Backup Manager chamar o método onBackup().

Ao usar esses parâmetros, implemente o método onBackup() para fazer o seguinte:

  1. Conferir se os dados mudaram desde o último backup, comparando o oldState com os dados atuais. A forma como os dados são lidos no oldState depende de como eles foram gravados originalmente no newState. Consulte a etapa 3. A forma mais fácil de registrar o estado de um arquivo é com o carimbo de data/hora da última modificação. Por exemplo, veja como ler e comparar um carimbo de data/hora do oldState:

    Kotlin

    val instream = FileInputStream(oldState.fileDescriptor)
    val dataInputStream = DataInputStream(instream)
    try {
       // Get the last modified timestamp from the state file and data file
       val stateModified = dataInputStream.readLong()
       val fileModified: Long = dataFile.lastModified()
       if (stateModified != fileModified) {
           // The file has been modified, so do a backup
           // Or the time on the device changed, so be safe and do a backup
       } else {
           // Don't back up because the file hasn't changed
           return
       }
    } catch (e: IOException) {
       // Unable to read state file... be safe and do a backup
    }
    

    Java

    // Get the oldState input stream
    FileInputStream instream = new FileInputStream(oldState.getFileDescriptor());
    DataInputStream in = new DataInputStream(instream);
    
    try {
       // Get the last modified timestamp from the state file and data file
       long stateModified = in.readLong();
       long fileModified = dataFile.lastModified();
    
       if (stateModified != fileModified) {
           // The file has been modified, so do a backup
           // Or the time on the device changed, so be safe and do a backup
       } else {
           // Don't back up because the file hasn't changed
           return;
       }
    } catch (IOException e) {
       // Unable to read state file... be safe and do a backup
    }
    

    Se não houve nenhuma mudança e você não precisa de backup, pule para a etapa 3.

  2. Caso seus dados mudem em comparação ao oldState, grave os dados atuais em data para fazer backup no armazenamento em nuvem.

    É preciso gravar cada bloco de dados como uma entidade na BackupDataOutput. Uma entidade é um registro de dados binários nivelado, identificado por uma string de chave única. Assim, o conjunto de dados armazenado em backup é, conceitualmente, um conjunto de pares de chave-valor.

    Para adicionar uma entidade ao seu conjunto de dados de backup, é necessário:

    1. chamar o método writeEntityHeader(), transmitindo uma chave de string única para os dados que você está prestes a gravar e para o tamanho dos dados;

    2. chamar o método writeEntityData(), transmitindo um buffer de bytes contendo os dados e o número de bytes a serem gravados do buffer, que precisa corresponder ao tamanho transmitido para o método writeEntityHeader().

    Por exemplo, o código a seguir nivela alguns dados em um fluxo de bytes e o grava em uma única entidade:

    Kotlin

    val buffer: ByteArray = ByteArrayOutputStream().run {
       DataOutputStream(this).apply {
           writeInt(playerName)
           writeInt(playerScore)
       }
       toByteArray()
    }
    val len: Int = buffer.size
    data.apply {
       writeEntityHeader(TOPSCORE_BACKUP_KEY, len)
       writeEntityData(buffer, len)
    }
    

    Java

    // Create buffer stream and data output stream for our data
    ByteArrayOutputStream bufStream = new ByteArrayOutputStream();
    DataOutputStream outWriter = new DataOutputStream(bufStream);
    // Write structured data
    outWriter.writeUTF(playerName);
    outWriter.writeInt(playerScore);
    // Send the data to the Backup Manager via the BackupDataOutput
    byte[] buffer = bufStream.toByteArray();
    int len = buffer.length;
    data.writeEntityHeader(TOPSCORE_BACKUP_KEY, len);
    data.writeEntityData(buffer, len);
    

    Faça isso para cada bloco de dados que você quer armazenar em backup. A forma como você divide os dados em entidades fica a seu critério. Você pode até usar apenas uma entidade.

  3. Independentemente de ter feito um backup ou não na etapa 2, grave uma representação dos dados atuais no ParcelFileDescriptor do newState. O Backup Manager armazena esse objeto localmente como uma representação dos dados que estão em backup no momento. Ele o transmitirá de volta para você como oldState na próxima vez que chamar o método onBackup(), para que você possa determinar se outro backup é necessário, como mostrado na etapa 1. Se você não gravar o estado atual dos dados nesse arquivo, o oldState estará vazio durante o próximo callback.

    O exemplo a seguir salva uma representação dos dados atuais no newState, usando o carimbo de data/hora da última modificação do arquivo:

    Kotlin

    val modified = dataFile.lastModified()
    FileOutputStream(newState.fileDescriptor).also {
       DataOutputStream(it).apply {
           writeLong(modified)
       }
    }
    

    Java

    FileOutputStream outstream = new FileOutputStream(newState.getFileDescriptor());
    DataOutputStream out = new DataOutputStream(outstream);
    
    long modified = dataFile.lastModified();
    out.writeLong(modified);
    

Fazer uma restauração

No momento de restaurar os dados do app, o Backup Manager chama o método onRestore() do agente de backup. Ao chamar esse método, o Backup Manager exibe os dados de backup para que você possa restaurá-los no dispositivo.

Apenas o Backup Manager pode chamar onRestore(), o que acontece automaticamente quando o sistema instala o app e encontra dados de backup existentes.

Quando o Backup Manager chama o método onRestore(), ele transmite três parâmetros:

data
Um objeto BackupDataInput, que permite ler os dados de backup.
appVersionCode
Um número inteiro representando o valor do atributo do manifesto android:versionCode do app, como era no momento em que esses dados foram armazenados em backup. Essa opção possibilita verificar a versão atual do app e determinar se o formato de dados é compatível. Para ver mais informações sobre como usar esse recurso para processar diferentes versões de dados de restauração, consulte Verificar a versão dos dados de restauração.
newState
Um ParcelFileDescriptor aberto de leitura/gravação, que aponta para um arquivo em que você precisa gravar o estado de backup final fornecido com data. Esse objeto será retornado como o oldState na próxima vez que o onBackup() for chamado. Lembre-se de que você precisa gravar o mesmo objeto newState no callback onBackup(). Fazer isso neste callback também garante que o objeto oldState enviado ao método onBackup() seja válido mesmo na primeira vez que onBackup() for chamado depois da restauração do dispositivo.

Na implementação de onRestore(), é preciso chamar readNextHeader() nos data para iterar todas as entidades no conjunto de dados. Para cada entidade encontrada, faça o seguinte:

  1. Para acessar a chave da entidade, use getKey().
  2. Compare a chave da entidade com uma lista de chaves-valor conhecidas, que precisam ter sido declaradas como strings estáticas finais dentro da classe BackupAgent. Quando a chave corresponder a uma das strings de chave conhecidas, insira uma instrução para extrair os dados da entidade e salve-os no dispositivo:

    1. Veja o tamanho dos dados da entidade usando getDataSize() e crie uma matriz de bytes desse tamanho.
    2. Chame readEntityData() e transmita a ele a matriz de bytes, que é para onde os dados irão, e especifique o deslocamento inicial e o tamanho a ser lido.
    3. Agora, sua matriz de bytes estará cheia. Leia os dados e grave-os no dispositivo como você quiser.
  3. Depois de ler e gravar os dados no dispositivo, grave o estado deles no parâmetro newState da mesma forma que faria durante o método onBackup().

Por exemplo, veja como restaurar os dados armazenados em backup de acordo com o exemplo da seção anterior:

Kotlin

@Throws(IOException::class)
override fun onRestore(data: BackupDataInput, appVersionCode: Int,
                       newState: ParcelFileDescriptor) {
    with(data) {
        // There should be only one entity, but the safest
        // way to consume it is using a while loop
        while (readNextHeader()) {
            when(key) {
                TOPSCORE_BACKUP_KEY -> {
                    val dataBuf = ByteArray(dataSize).also {
                        readEntityData(it, 0, dataSize)
                    }
                    ByteArrayInputStream(dataBuf).also {
                        DataInputStream(it).apply {
                            // Read the player name and score from the backup data
                            playerName = readUTF()
                            playerScore = readInt()
                        }
                        // Record the score on the device (to a file or something)
                        recordScore(playerName, playerScore)
                    }
                }
                else -> skipEntityData()
            }
        }
    }

    // Finally, write to the state blob (newState) that describes the restored data
    FileOutputStream(newState.fileDescriptor).also {
        DataOutputStream(it).apply {
            writeUTF(playerName)
            writeInt(mPlayerScore)
        }
    }
}

Java

@Override
public void onRestore(BackupDataInput data, int appVersionCode,
                      ParcelFileDescriptor newState) throws IOException {
    // There should be only one entity, but the safest
    // way to consume it is using a while loop
    while (data.readNextHeader()) {
        String key = data.getKey();
        int dataSize = data.getDataSize();

        // If the key is ours (for saving top score). Note this key was used when
        // we wrote the backup entity header
        if (TOPSCORE_BACKUP_KEY.equals(key)) {
            // Create an input stream for the BackupDataInput
            byte[] dataBuf = new byte[dataSize];
            data.readEntityData(dataBuf, 0, dataSize);
            ByteArrayInputStream baStream = new ByteArrayInputStream(dataBuf);
            DataInputStream in = new DataInputStream(baStream);

            // Read the player name and score from the backup data
            playerName = in.readUTF();
            playerScore = in.readInt();

            // Record the score on the device (to a file or something)
            recordScore(playerName, playerScore);
        } else {
            // We don't know this entity key. Skip it. (Shouldn't happen.)
            data.skipEntityData();
        }
    }

    // Finally, write to the state blob (newState) that describes the restored data
    FileOutputStream outstream = new FileOutputStream(newState.getFileDescriptor());
    DataOutputStream out = new DataOutputStream(outstream);
    out.writeUTF(playerName);
    out.writeInt(mPlayerScore);
}

Nesse exemplo, o parâmetro appVersionCode transmitido para o método onRestore() não é usado. No entanto, você pode querer usá-lo se escolher fazer um backup quando a versão do app usada pelo usuário regredir, por exemplo, ele mudar da versão 1.5 do app para a 1.0. Para saber mais, consulte a próxima seção.

Verificar a versão dos dados de restauração

Quando o Backup Manager salva os dados no armazenamento em nuvem, ele inclui automaticamente a versão do app, conforme definido pelo atributo android:versionCode do arquivo de manifesto. Antes do Backup Manager chamar o agente de backup para restaurar os dados, ele analisa o android:versionCode do app instalado e o compara com o valor registrado no conjunto de dados de restauração. Se a versão registrada no conjunto de dados de restauração for mais recente do que a versão do app no dispositivo, o usuário terá feito downgrade do app. Nesse caso, o Backup Manager cancelará a operação de restauração do app e não chamará o método onRestore(), porque o conjunto de restauração será considerado irrelevante para uma versão mais antiga.

É possível substituir esse comportamento pelo atributo android:restoreAnyVersion. Defina esse atributo como true para indicar que você quer restaurar o app, independentemente da versão do conjunto de restauração. O valor padrão é false. Se você defini-lo como true, o Backup Manager ignorará o android:versionCode e chamará o método onRestore() em todos os casos. Ao fazer isso, você poderá conferir manualmente a diferença de versão no método onRestore() e seguir qualquer etapa necessária para tornar os dados compatíveis se as versões não corresponderem.

Para lidar com versões diferentes durante uma operação de restauração, o método onRestore() transmite o código de versão incluído no conjunto de dados de restauração como o parâmetro appVersionCode. Em seguida, é possível consultar o código da versão atual do app usando o campo PackageInfo.versionCode. Exemplo:

Kotlin

val info: PackageInfo? = try {
    packageManager.getPackageInfo(packageName, 0)
} catch (e: PackageManager.NameNotFoundException) {
    null
}

val version: Int = info?.versionCode ?: 0

Java

PackageInfo info;
try {
    String name = getPackageName();
    info = getPackageManager().getPackageInfo(name, 0);
} catch (NameNotFoundException nnfe) {
    info = null;
}

int version;
if (info != null) {
    version = info.versionCode;
}

Então, basta comparar a version recebida das PackageInfo usando o appVersionCode transmitido para o método onRestore().

Solicitar um backup

Você pode solicitar uma operação de backup a qualquer momento chamando o método dataChanged(). Esse método notifica o Backup Manager que você quer fazer backup dos dados usando o agente de backup. Em seguida, o Backup Manager chamará o método onBackup() do agente de backup no futuro. Normalmente, é necessário solicitar um backup sempre que os dados mudam, por exemplo, quando o usuário muda uma preferência de um app que você quer armazenar em backup. Se você chamar o método dataChanged() várias vezes seguidas antes do Backup Manager solicitar um backup, seu agente ainda receberá apenas uma chamada para onBackup().

Solicitar uma restauração

Durante a vida normal do app, não é necessário solicitar uma operação de restauração. O sistema verifica automaticamente os dados de backup e executa uma restauração quando o app é instalado.

Migrar para o Backup automático

Você pode fazer a transição do app para backups de dados completos configurando android:fullBackupOnly como true no elemento <application> do arquivo de manifesto. Quando executado em um dispositivo com o Android 5.1 (nível 22 da API) ou versões anteriores, o app ignorará esse valor no manifesto e continuará fazendo backups de chave-valor. Quando executado em um dispositivo com o Android 6.0 (nível 23 da API) ou versões mais recente, o app fará o backup automático em vez do backup de chave-valor.

Privacidade do usuário

No Google, estamos cientes da confiança que os usuários depositam em nós e da nossa responsabilidade de proteger a privacidade deles. O Google transmite os dados de backup para os próprios servidores e fora deles com segurança para oferecer recursos de backup e restauração. O Google trata esses dados como informações pessoais de acordo com a nossa Política de Privacidade.

Além disso, os usuários podem desativar o recurso de backup de dados nas configurações de backup do sistema Android. Quando o usuário desativa o backup, o Android Backup Service exclui todos os dados de backup salvos. O usuário pode ativar novamente o backup no dispositivo, mas o Android Backup Service não restaurará os dados excluídos anteriormente.