O Google tem o compromisso de promover a igualdade racial para as comunidades negras. Saiba como.

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

O Android Backup Service oferece backup e restauração de 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.

A quantidade de dados é limitada a 5 MB por usuário do app e nenhuma cobrança é gerada para armazenar os dados de backup.

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

Observação: para a maior parte dos apps, o backup automático é usado para backup e restauração. Seu app só pode implementar o backup automático e o backup chave-valor, não ambos. O backup automático não exige código e faz backup de arquivos inteiros, enquanto o backup de chave-valor exige código para definir o conteúdo do backup explicitamente na forma de pares de chave-valor.

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 com o atributo android:backupAgent;
  2. registrar seu app no Android Backup Service;
  3. definir um agente de backup de uma das seguintes maneiras:
    1. Estender o BackupAgent

      A classe BackupAgent fornece a interface central com que seu app se comunica com o Backup Manager. Se você estender essa classe diretamente, será necessário modificar onBackup() e onRestore() para processar as operações de backup e restauração dos seus dados.

    2. Ou

    3. Estender o BackupAgentHelper

      A classe BackupAgentHelper fornece um wrapper conveniente em torno da classe BackupAgent, o que diminui a quantidade de código necessária. No seu BackupAgentHelper, é necessário usar um ou mais objetos "helper", que fazem o backup e a restauração de certos tipos de dados automaticamente. Assim, você não precisa implementar onBackup() e onRestore(). A menos que você precise ter controle total dos backups do seu app, recomendamos o uso do BackupAgentHelper para processar os backups do seu app.

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

Declarar o agente de backup no manifesto

Essa é a etapa mais fácil. Depois de decidir o nome da classe do agente de backup, declare-o no manifesto com o atributo android:backupAgent na tag <application>.

Exemplo:

<manifest ... >
    ...
    <application android:label="MyApplication"
                 android:backupAgent="MyBackupAgent">
        <activity ... >
            ...
        </activity>
    </application>
</manifest>

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

Registrar-se no Android Backup Service

O Google oferece uma transferência de backup com o Android Backup Service para a maioria dos dispositivos com o Android versão 2.2 ou mais recente.

Para que o app faça backup usando o Android Backup Service, é necessário registrá-lo no serviço para receber uma chave do Backup Service e declará-la no manifesto do Android.

Para receber a chave do Backup Service, registre-se no Android Backup Service . Ao se registrar, você receberá uma chave do Backup Service e o código XML <meta-data> adequado para o arquivo de manifesto do Android, que precisa ser incluído como filho do elemento <application>. Exemplo:

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

O android:name precisa ser "com.google.android.backup.api_key", e o android:value precisa ser a chave do Backup Service recebida ao se registrar no Android Backup Service.

Caso você tenha vários apps, é necessário registrar cada um deles usando o respectivo nome de pacote.

Observação: não há garantia de que a transferência de backup oferecida pelo Android Backup Service esteja disponível em todos os dispositivos Android compatíveis com backup. Alguns dispositivos podem oferecer compatibilidade por meio de outra transferência, outros podem ser incompatíveis, e não há como seu app saber qual é a transferência usada no dispositivo. No entanto, se você implementar o backup do app, sempre inclua uma chave para o Android Backup Service de forma que o app possa fazer backup quando o dispositivo usar a transferência oferecida por esse serviço. Se o dispositivo não usar o Android Backup Service, o elemento <meta-data> com a chave do Backup Service será ignorado.

Estender o BackupAgentHelper

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

Sua implementação de BackupAgentHelper precisa usar um ou mais auxiliares de backup. Um auxiliar de backup é um componente especializado que 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. Instanciar um exemplo da classe auxiliar visada. No construtor da classe, é necessário especificar os arquivos adequados que você quer armazenar em backup.
  2. Chamar 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 denominado user_preferences, um agente de backup completo que usa BackupAgentHelper é semelhante a:

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);
    }
}

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

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

Observação: os métodos de SharedPreferences são seguros para linha de execução. Portanto, é possível ler e gravar com segurança o arquivo de preferências compartilhadas do agente de backup e de outras atividades.

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 por getFilesDir(), que é o mesmo local em que openFileOutput() grava arquivos).

Por exemplo, para fazer backup de dois arquivos denominados scores e stats, um agente de backup que usa BackupAgentHelper é semelhante a:

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 a linha de execução. Para garantir que o agente de backup não leia ou grave seus arquivos ao mesmo tempo que suas 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, é necessário 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();

Em seguida, crie uma instrução sincronizada com 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 modificar onBackup() e onRestore() para sincronizar as operações de backup e restauração com o mesmo bloqueio intrínseco. Por exemplo, o caso com MyFileBackupAgent acima precisa dos seguintes métodos:

Kotlin

@Throws(IOException::class)
override fun onBackup(
        oldState: ParcelFileDescriptor,
        data: BackupDataOutput,
        newState: ParcelFileDescriptor
) {
    // Hold the lock while the FileBackupHelper performs backup
    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 backup
    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);
    }
}

Isso é tudo o que você precisa para fazer o backup e a restauração de dados com BackupAgentHelper.

Estender o BackupAgent

A maior parte dos apps não precisa estender a classe BackupAgent diretamente, mas deve estender o BackupAgentHelper para aproveitar as classes auxiliares integradas que fazem o backup e a restauração dos seus arquivos automaticamente. No entanto, estenda BackupAgent diretamente se uma das ações a seguir for necessária:

  • 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, crie 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 daquela dos dados de backup. Para mais informações, consulte Verificar a versão dos dados de restauração.
  • 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ê tiver um banco de dados SQLite que pretende restaurar quando o usuário reinstalar seu app, será necessário criar um BackupAgent personalizado que leia os dados corretos durante uma operação de backup. Em seguida, crie sua 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 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 a partir do dispositivo, e os dados que serão armazenados em backup são passados para o Backup Manager, conforme descrito abaixo em Fazer um backup.
onRestore()
O Backup Manager chama esse método durante uma operação de restauração. Esse método exibe seus dados de backup, que podem ser usados pelo app para restaurar o estado anterior, conforme descrito abaixo em Fazer uma restauração.

O sistema chama esse método para restaurar qualquer dado de backup quando o usuário reinstala seu app, mas o app também pode solicitar uma restauração.

Fazer um backup

No momento de fazer backup dos dados do app, o Backup Manager chama seu método onBackup(). É nessa etapa que você precisa 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 seu agente de backup. Sempre que os dados do app mudarem e você quiser fazer um backup, será necessário solicitar uma operação de backup chamando dataChanged(). Consulte Solicitar um backup para mais informações. 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.

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

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

oldState
Um ParcelFileDescriptor aberto, somente leitura, que aponta para o último estado de backup fornecido pelo app. Esses dados de backup não são do armazenamento em nuvem, mas uma representação local dos dados que foram armazenados na última vez que onBackup() foi chamado, conforme definido por newState abaixo ou a partir de onRestore(). Veja mais informações sobre isso na próxima seção. Como onBackup() não permite 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 entregar 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 entregues ao data (uma representação pode ser apenas o carimbo de data/hora da última modificação do seu arquivo). Esse objeto será retornado como oldState na próxima vez que o Backup Manager chamar o método onBackup(). Se você não gravar os dados de backup em newState, oldState apontará um arquivo vazio na próxima vez que o Backup Manager chamar onBackup().

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

  1. Verifique se os dados mudaram desde o último backup, comparando oldState aos dados atuais. A forma como os dados são lidos em oldState depende de como eles foram gravados originalmente em newState (veja 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 de 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. Se seus dados mudaram em comparação a oldState, grave os dados atuais em data para fazer backup no armazenamento em nuvem.

    É preciso gravar cada bloco de dados como uma “entidade” em 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 writeEntityHeader(), passando uma chave de string única para os dados que você está prestes a gravar e para o tamanho dos dados;
    2. chamar writeEntityData(), passando um buffer de bytes contendo seus dados e o número de bytes a serem gravados do buffer (que deve corresponder ao tamanho passado para 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 (e é possível usar somente uma entidade).

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

    O exemplo a seguir salva uma representação dos dados atuais em newState, por meio do 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);
    
    

Atenção: se os dados do app estiverem salvos em um arquivo, use instruções sincronizadas ao acessar o arquivo para que o agente de backup não o leia enquanto uma atividade no app também estiver fazendo a gravação.

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 seus dados de backup para que você possa restaurá-los no dispositivo.

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

Observação: ao desenvolver seu app, também é possível solicitar uma operação de restauração com a ferramenta bmgr.

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

data
Um BackupDataInput, que permite ler seus dados de backup.
appVersionCode
Um número inteiro que representa o valor do atributo do manifesto android:versionCode do seu app, referente ao momento em que esses dados foram armazenados em backup. Essa opção permite 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 parâmetro para processar diferentes versões de dados de restauração, consulte a seção abaixo sobre 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 oldState na próxima vez que onBackup() for chamado. Lembre-se de que você precisa gravar o mesmo objeto newState no callback onBackup(). Fazer isso também aqui garante que o objeto oldState entregue a onBackup() seja válido mesmo na primeira vez que onBackup() é chamado depois da restauração do dispositivo.

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

  1. Para conseguir a chave da entidade, use getKey().
  2. Compare a chave da entidade com uma lista de chaves-valor conhecidas, que devem 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 com getDataSize() e crie uma matriz de bytes desse tamanho.
    2. Chame readEntityData() e passe 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. Sua matriz de bytes já está cheia e você pode ler os dados e gravá-los no dispositivo da forma que preferir.
  3. Depois de ler e gravar os dados no dispositivo, grave o estado deles no parâmetro newState da mesma forma que faria durante 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 passado para onRestore() não é usado. No entanto, use-o se você optou por fazer backup quando a versão do app utilizada pelo usuário regride (por exemplo, o usuário passou da versão 1.5 do seu app para a 1.0). Para mais informações, consulte a seção sobre Verificar a versão dos dados de restauração.

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

Quando o Backup Manager salva seus dados no armazenamento em nuvem, ele inclui automaticamente a versão do seu app, conforme definido pelo atributo android:versionCode do arquivo de manifesto. Antes de o Backup Manager chamar seu agente de backup para restaurar seus 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 modificar esse comportamento com o atributo android:restoreAnyVersion. Esse atributo é "true" ou "false" para indicar se você quer restaurar o app, independentemente da versão do conjunto de restauração. O valor padrão é "false". Se você o definir como "true" o Backup Manager ignorará o android:versionCode e chamará o método onRestore() em todos os casos. Ao fazer isso, você poderá verificar manualmente a diferença de versão no seu método onRestore() e seguir qualquer etapa necessária para tornar os dados compatíveis se as versões forem conflitantes.

Para processar versões diferentes durante uma operação de restauração, o método onRestore() passa 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 de versão do app atual com 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 adquirida de PackageInfo com o appVersionCode passado para onRestore().

Cuidado: é preciso entender as consequências de se configurar android:restoreAnyVersion como "true" para seu app. Se cada versão do app compatível com backup não considerar corretamente as variações no formato de dados durante onRestore(), os dados no dispositivo poderão ser salvos em um formato incompatível com a versão atualmente instalada no dispositivo.

Solicitar um backup

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

Observação: ao desenvolver seu app, você pode solicitar um backup e iniciar uma operação de backup imediata com a ferramenta bmgr.

Solicitar uma restauração

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

Observação: ao desenvolver seu app, você pode solicitar uma operação de restauração com a ferramenta bmgr.

Migrar para o backup automático

Você pode fazer a transição do seu 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 (API de nível 22) ou anterior, o app ignora esse valor no manifesto e continua realizando backups de chave-valor. Quando executado em um dispositivo com o Android 6.0 (API de nível 23) ou posterior, o app realiza 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. O Google transmite os dados de backup entre os próprios servidores com segurança para oferecer recursos de backup e restauração. O Google trata esses dados como informações pessoais de acordo com a 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.