Sauvegarder des paires clé-valeur avec Android Backup Service

Android Backup Service permet de sauvegarder et de restaurer dans le cloud des données clé-valeur issues de votre application Android. Lors d'une opération de sauvegarde de clé-valeur, les données de sauvegarde de l'application sont transmises au service de transfert des sauvegardes de l'appareil. Si l'appareil utilise par défaut le service de transfert des sauvegardes de Google, les données sont transmises à Android Backup Service pour archivage.

Les données sont limitées à 5 Mo par utilisateur de votre application. Le stockage des données de sauvegarde se fait sans frais.

Pour obtenir une présentation des options de sauvegarde d'Android et des conseils sur les données à sauvegarder et à restaurer, consultez la page Présentation de la sauvegarde de données.

Implémenter la sauvegarde clé-valeur

Pour sauvegarder les données de votre application, vous devez implémenter un agent de sauvegarde. Votre agent de sauvegarde est appelé par le gestionnaire de sauvegarde lors des sauvegardes et des restaurations.

Pour implémenter un agent de sauvegarde, vous devez prendre les mesures suivantes :

  1. Déclarez votre agent de sauvegarde dans votre fichier manifeste avec l'attribut android:backupAgent.

  2. Définissez un agent de sauvegarde en effectuant l'une des opérations suivantes :

    • Étendre la classe BackupAgent

      La classe BackupAgent fournit l'interface centrale que votre application utilise pour communiquer avec le gestionnaire de sauvegarde. Si vous étendez cette classe directement, vous devez remplacer onBackup() et onRestore() pour gérer les opérations de sauvegarde et de restauration de vos données.

    • Étendre la classe BackupAgentHelper

      La classe BackupAgentHelper fournit un wrapper pratique pour la classe BackupAgent, ce qui réduit la quantité de code à écrire. Dans votre classe BackupAgentHelper, vous devez utiliser un ou plusieurs objets d'assistance, qui sauvegardent et restaurent automatiquement certains types de données, afin que vous n'ayez pas besoin d'implémenter onBackup() et onRestore() À moins que vous n'ayez besoin d'avoir un contrôle total sur les sauvegardes de votre application, nous vous recommandons d'utiliser BackupAgentHelper pour gérer les sauvegardes de votre application.

      Android fournit actuellement des assistants de sauvegarde qui sauvegardent et restaurent des fichiers complets depuis SharedPreferences et la mémoire de stockage interne.

Déclarer l'agent de sauvegarde dans votre fichier manifeste

Une fois que vous avez choisi le nom de classe de votre agent de sauvegarde, déclarez-le dans votre fichier manifeste à l'aide de l'attribut android:backupAgent dans la balise <application>.

Par exemple :

<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>

Pour les anciens appareils, nous vous recommandons d'ajouter la clé API <meta-data> à votre fichier manifeste Android. Android Backup Service ne nécessite plus de clé de service, mais certains appareils plus anciens peuvent toujours rechercher une clé lors de la sauvegarde. Définissez android:name sur com.google.android.backup.api_key et android:value sur unused.

À l'aide d'une valeur booléenne, l'attribut android:restoreAnyVersion vous permet d'indiquer si vous souhaitez restaurer les données de l'application, quelle que soit la version de l'application actuelle, ou si vous souhaitez que la restauration se fasse sur la version à l'origine des données de sauvegarde. La valeur par défaut est false. Pour en savoir plus, consultez la section Vérifier la version des données de restauration.

Étendre la classe BackupAgentHelper

Si vous souhaitez sauvegarder des fichiers complets à partir de SharedPreferences ou de la mémoire de stockage interne, vous devez compiler votre agent de sauvegarde à l'aide de BackupAgentHelper. La compilation de votre agent de sauvegarde avec BackupAgentHelper nécessite beaucoup moins de code que d'étendre la classe BackupAgent, car vous n'avez pas besoin d'implémenter onBackup() et onRestore().

Votre implémentation de BackupAgentHelper doit utiliser un ou plusieurs assistants de sauvegarde. Un assistant de sauvegarde est un composant spécialisé que BackupAgentHelper appelle pour effectuer des opérations de sauvegarde et de restauration d'un type de données particulier. Le framework Android fournit actuellement deux assistants différents :

Vous pouvez inclure plusieurs assistants dans votre classe BackupAgentHelper, mais un seul assistant est nécessaire par type de données. Autrement dit, si vous avez plusieurs fichiers SharedPreferences, vous n'avez besoin que d'un seul fichier SharedPreferencesBackupHelper.

Pour chaque assistant que vous souhaitez ajouter à votre classe BackupAgentHelper, vous devez procéder comme suit dans la méthode onCreate() :

  1. Instanciez une instance de la classe d'assistance souhaitée. Dans le constructeur de classe, vous devez spécifier le ou les fichiers que vous souhaitez sauvegarder.
  2. Appelez addHelper() pour ajouter l'assistant à votre classe BackupAgentHelper.

Les sections suivantes décrivent comment créer un agent de sauvegarde à l'aide de chacun des assistants disponibles.

Sauvegarder SharedPreferences

Lorsque vous instanciez une classe SharedPreferencesBackupHelper, vous devez inclure le nom d'un ou de plusieurs fichiers SharedPreferences.

Par exemple, pour sauvegarder un fichier SharedPreferences nommé user_preferences, un agent de sauvegarde complet utilisant BackupAgentHelper se présente comme suit :

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

La classe SharedPreferencesBackupHelper inclut tout le code nécessaire à la sauvegarde et à la restauration d'un fichier SharedPreferences.

Lorsque le gestionnaire de sauvegarde appelle onBackup() et onRestore(), la classe BackupAgentHelper appelle vos assistants de sauvegarde pour sauvegarder et restaurer les fichiers spécifiés.

Sauvegarder d'autres fichiers

Lorsque vous instanciez un FileBackupHelper, vous devez inclure le nom d'un ou de plusieurs fichiers enregistrés dans la mémoire de stockage interne de votre application, comme spécifié par getFilesDir(), qui correspond au lieu où openFileOutput() écrit des fichiers.

Par exemple, pour sauvegarder deux fichiers nommés scores et stats, un agent de sauvegarde utilisant BackupAgentHelper se présente comme suit :

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

La classe FileBackupHelper inclut tout le code nécessaire pour sauvegarder et restaurer les fichiers enregistrés dans la mémoire de stockage interne de votre application.

Cependant, la lecture et l'écriture dans des fichiers de la mémoire de stockage interne ne sont pas thread-safe. Pour vous assurer que votre agent de sauvegarde ne lit ou n'écrit pas vos fichiers en même temps que vos activités, vous devez utiliser des instructions synchronisées chaque fois que vous effectuez une lecture ou une écriture. Par exemple, dans toute activité de lecture et d'écriture du fichier, vous avez besoin d'un objet à utiliser comme verrou intrinsèque pour les instructions synchronisées :

Kotlin

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

Java

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

Ensuite, créez une instruction synchronisée avec ce verrou chaque fois que vous lisez ou écrivez des fichiers. Par exemple, voici une déclaration synchronisée pour écrire le dernier score d'un jeu dans un fichier :

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

Vous devez synchroniser vos instructions de lecture avec le même verrou.

Ensuite, dans votre classe BackupAgentHelper, vous devez remplacer onBackup() et onRestore() pour synchroniser les opérations de sauvegarde et de restauration avec le même verrou intrinsèque. Par exemple, l'exemple MyFileBackupAgent ci-dessus nécessite les méthodes suivantes :

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

Étendre la classe BackupAgent

La plupart des applications ne devraient pas avoir besoin d'étendre directement la classe BackupAgent, mais devraient plutôt étendre la classe BackupAgentHelper pour bénéficier des classes d'assistance intégrées qui sauvegardent et restaurent automatiquement vos fichiers. Toutefois, vous pourriez étendre directement la classe BackupAgent pour effectuer les opérations suivantes :

  • Gérer la version du format de vos données. Par exemple, si vous pensez avoir besoin de réviser le format dans lequel vous écrivez les données de votre application, vous pouvez créer un agent de sauvegarde pour vérifier la version de votre application lors d'une opération de restauration et effectuer toutes les opérations de compatibilité nécessaires si la version de l'appareil est différente de celle des données de sauvegarde. Pour en savoir plus, consultez la section Vérifier la version des données de restauration.
  • Spécifier les portions de données à sauvegarder. Au lieu de sauvegarder un fichier entier, vous pouvez spécifier les portions de données à sauvegarder et la manière dont chaque portion est ensuite restaurée sur l'appareil. Cela peut également vous aider à gérer différentes versions, car vous lisez et écrivez vos données en tant qu'entités uniques plutôt qu'en tant que fichiers complets.
  • Sauvegarder des données dans une base de données. Si vous disposez d'une base de données SQLite que vous souhaitez restaurer lorsque l'utilisateur réinstalle votre application, vous devez créer un fichier BackupAgent personnalisé qui lit les données appropriées lors d'une opération de sauvegarde, puis créer votre table et insérer les données lors d'une opération de restauration.

Si vous n'avez pas besoin d'effectuer les tâches ci-dessus et que vous souhaitez sauvegarder des fichiers complets à partir de SharedPreferences ou de la mémoire de stockage interne, consultez la section Étendre la classe BackupAgentHelper.

Méthodes requises

Lorsque vous créez une classe BackupAgent, vous devez implémenter les méthodes de rappel suivantes :

onBackup()
Le gestionnaire de sauvegarde appelle cette méthode lorsque vous demandez une sauvegarde. Avec cette méthode, vous lisez les données de l'application à partir de l'appareil et transmettez les données que vous souhaitez sauvegarder au gestionnaire de sauvegarde, comme décrit dans la section Effectuer une sauvegarde
.
onRestore()

Le gestionnaire de sauvegarde appelle cette méthode lors d'une opération de restauration. Cette méthode fournit vos données de sauvegarde, que votre application peut utiliser pour restaurer son état précédent, comme décrit dans la section Effectuer une restauration.

Le système appelle cette méthode pour restaurer toutes les données de sauvegarde lorsque l'utilisateur réinstalle votre application, mais votre application peut également demander une restauration.

Effectuer une sauvegarde

Une requête de sauvegarde n'appelle pas immédiatement votre méthode onBackup(). À la place, le gestionnaire de sauvegarde attend une heure appropriée, puis effectue une sauvegarde pour toutes les applications qui ont demandé une sauvegarde depuis la dernière sauvegarde. C'est à ce stade que vous devez fournir les données de votre application au gestionnaire de sauvegarde afin qu'elles puissent être enregistrées dans le stockage cloud.

Seul le gestionnaire de sauvegarde peut appeler la méthode onBackup() de votre agent de sauvegarde. Chaque fois que les données de votre application changent et que vous souhaitez effectuer une sauvegarde, vous devez demander une opération de sauvegarde en appelant dataChanged(). Pour en savoir plus, consultez Demander une sauvegarde.

Conseil : Lors du développement de votre application, vous pouvez lancer une opération de sauvegarde immédiate à partir du gestionnaire de sauvegarde à l'aide de l'outil bmgr.

Lorsque le gestionnaire de sauvegarde appelle votre méthode onBackup(), il transmet trois paramètres :

oldState
Un
ParcelFileDescriptor ouvert et en lecture seule pointant vers le dernier état de sauvegarde fourni par votre application. Il ne s'agit pas des données de sauvegarde issues du stockage cloud, mais d'une représentation locale des données sauvegardées lors du dernier appel de onBackup(), comme défini par newState ou à partir de onRestore(). onRestore() est abordé dans la section suivante. Étant donné que onBackup() ne vous permet pas de lire les données de sauvegarde existantes dans le stockage cloud, vous pouvez utiliser cette représentation locale pour déterminer si vos données ont changé depuis la dernière sauvegarde.
data
Un objet BackupDataOutput que vous utilisez pour transmettre vos données de sauvegarde au gestionnaire de sauvegarde.
newState
Un
ParcelFileDescriptor ouvert et de lecture/écriture pointant vers un fichier dans lequel vous devez écrire une représentation des données que vous avez fournies à data. Cette représentation peut simplement être la date et l'heure de la dernière modification de votre fichier. Cet objet est renvoyé en tant que oldState la prochaine fois que le gestionnaire de sauvegarde appelle votre méthode onBackup(). Si vous n'écrivez pas vos données de sauvegarde dans newState, oldState pointera vers un fichier vide la prochaine fois que le gestionnaire de sauvegarde appellera onBackup().

À l'aide de ces paramètres, implémentez votre méthode onBackup() pour effectuer les opérations suivantes :

  1. Vérifiez si vos données ont changé depuis la dernière sauvegarde en comparant oldState à vos données actuelles. La manière dont vous lisez les données dans oldState dépend de la manière dont vous les avez écrites dans newState (voir l'étape 3). La méthode la plus simple pour enregistrer l'état d'un fichier consiste à utiliser la date et l'heure de sa dernière modification. Par exemple, voici comment lire et comparer une date et une heure à partir 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
    }
    

    Si rien n'a changé et que vous n'avez pas besoin d'effectuer de sauvegarde, passez à l'étape 3.

  2. Si vos données ont changé par rapport à oldState, écrivez les données actuelles dans data pour les sauvegarder dans le stockage cloud.

    Vous devez écrire chaque fragment de données en tant qu'entité dans BackupDataOutput. Une entité est un enregistrement de données binaires aplati qui est identifié par une chaîne de clé unique. Ainsi, conceptuellement, l'ensemble de données que vous sauvegardez est un ensemble de paires clé-valeur.

    Pour ajouter une entité à votre ensemble de données de sauvegarde, procédez comme suit :

    1. Appelez writeEntityHeader() en transmettant une clé de chaîne unique pour les données que vous allez écrire et la taille des données.

    2. Appelez writeEntityData() en transmettant un tampon d'octets contenant vos données et le nombre d'octets à écrire à partir du tampon. La taille doit correspondre à la taille transmise à writeEntityHeader().

    Par exemple, le code suivant aplatit certaines données dans un flux d'octets et les écrit dans une seule entité :

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

    Procédez ainsi pour chaque donnée à sauvegarder. C'est vous qui déterminez la répartition de vos données en entités. Vous pouvez même n'utiliser qu'une seule entité.

  3. Que vous effectuiez ou non une sauvegarde (à l'étape 2), écrivez une représentation des données actuelles dans le ParcelFileDescriptor newState. Le gestionnaire de sauvegarde conserve cet objet localement en tant que représentation des données actuellement sauvegardées. Il vous renvoie cette valeur en tant que oldState la prochaine fois qu'il appelle onBackup(). Vous pouvez ainsi déterminer si une autre sauvegarde est nécessaire, comme traité à l'étape 1. Si vous n'écrivez pas l'état des données actuel dans ce fichier, oldState sera vide lors du prochain rappel.

    L'exemple suivant enregistre une représentation des données actuelles dans newState à l'aide de la date et de l'heure de dernière modification du fichier :

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

Effectuer une restauration

Au moment de restaurer les données de votre application, le gestionnaire de sauvegarde appelle la méthode onRestore() de votre agent de sauvegarde. Lorsque cette méthode est appelée, le gestionnaire de sauvegarde transmet vos données de sauvegarde afin que vous puissiez les restaurer sur l'appareil.

Seul le gestionnaire de sauvegarde peut appeler onRestore(), ce qui se produit automatiquement lorsque le système installe votre application et trouve des données de sauvegarde existantes.

Lorsque le gestionnaire de sauvegarde appelle votre méthode onRestore(), il transmet trois paramètres :

data
Un objet BackupDataInput qui vous permet de lire vos données de sauvegarde.
appVersionCode
Un entier représentant la valeur de l'attribut de manifeste android:versionCode de votre application au moment où ces données ont été sauvegardées. Vous pouvez l'utiliser pour vérifier la version actuelle de l'application et déterminer si le format de données est compatible. Pour en savoir plus sur la manière de l'utiliser pour gérer différentes versions des données de restauration, consultez la section Vérifier la version des données de restauration.
newState
Un ParcelFileDescriptor ouvert et de lecture/écriture pointant vers un fichier dans lequel vous devez écrire l'état de sauvegarde final fourni avec data. Cet objet est renvoyé en tant que oldState la prochaine fois que onBackup() est appelé. Rappelez-vous que vous devez également écrire le même objet newState dans le rappel onBackup(). Cela garantit ainsi que l'objet oldState attribué à onBackup() est valide, même la première fois que onBackup() est appelé après la restauration de l'appareil.

Dans votre implémentation de onRestore(), vous devez appeler readNextHeader() sur data pour itérer toutes les entités de l'ensemble de données. Pour chaque entité trouvée, procédez comme suit :

  1. Obtenez la clé de l'entité à l'aide de getKey().
  2. Comparez la clé de l'entité à une liste de valeurs de clé connues que vous auriez déclarées en tant que chaînes finales statiques dans votre classe BackupAgent. Lorsque la clé correspond à l'une de vos chaînes de clés connues, saisissez-la dans une instruction pour extraire les données de l'entité et les enregistrer sur l'appareil :

    1. Obtenez la taille des données de l'entité avec getDataSize(), puis créez un tableau d'octets de cette taille.
    2. Appelez readEntityData() et transmettez-lui le tableau d'octets, c'est-à-dire l'emplacement des données. Spécifiez alors le décalage de début, ainsi que la taille à lire.
    3. Votre tableau d'octets est maintenant plein. Lisez les données et écrivez-les sur l'appareil comme vous le souhaitez.
  3. Après avoir lu et réécrit vos données sur l'appareil, écrivez l'état de vos données dans le paramètre newState de la même manière que lors de l'exécution de onBackup().

Voici par exemple comment restaurer les données sauvegardées dans l'exemple de la section précédente :

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

Dans cet exemple, le paramètre appVersionCode transmis à onRestore() n'est pas utilisé. Toutefois, il peut être utile de l'utiliser si vous avez choisi d'effectuer une sauvegarde alors que la version de l'application de l'utilisateur a été rétrogradée (par exemple, si l'utilisateur est passé de la version 1.5 à la version 1.0 de votre application). Pour en savoir plus, consultez la section suivante.

Vérifier la version des données de restauration

Lorsque le gestionnaire de sauvegarde enregistre vos données dans le stockage cloud, il inclut automatiquement la version de votre application, telle que définie par l'attribut android:versionCode de votre fichier manifeste. Avant que le gestionnaire de sauvegarde n'appelle votre agent de sauvegarde pour restaurer vos données, il compare l'attribut android:versionCode de l'application installée à la valeur enregistrée dans l'ensemble de données de restauration. Si la version enregistrée dans l'ensemble de données de restauration est plus récente que celle de l'application sur l'appareil, cela signifie que l'utilisateur est revenu à une version antérieure. Dans ce cas, le gestionnaire de sauvegarde annule l'opération de restauration de votre application et n'appelle pas la méthode onRestore(), car il considère que l'ensemble de restauration n'a pas de sens pour l'ancienne version.

Vous pouvez ignorer ce comportement avec l'attribut android:restoreAnyVersion. Définissez cet attribut sur true pour indiquer que vous souhaitez restaurer l'application, quelle que soit la version de l'ensemble de restauration. La valeur par défaut est false. Si vous définissez ce paramètre sur true, le gestionnaire de sauvegarde ignore android:versionCode et appelle votre méthode onRestore() dans tous les cas. En procédant ainsi, vous pouvez vérifier manuellement la différence de version dans votre méthode onRestore() et prendre les mesures nécessaires pour rendre les données compatibles si les versions ne correspondent pas.

Pour vous aider à gérer différentes versions lors d'une opération de restauration, la méthode onRestore() vous transmet le code de version inclus avec l'ensemble de données de restauration défini en tant que paramètre appVersionCode. Vous pouvez ensuite interroger le code de version actuel de l'application à l'aide du champ PackageInfo.versionCode. Par exemple :

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

Comparez ensuite la version acquise à partir de PackageInfo avec le appVersionCode transmis dans onRestore().

Demander une sauvegarde

Vous pouvez demander une opération de sauvegarde à tout moment en appelant dataChanged(). Cette méthode informe le gestionnaire de sauvegarde que vous souhaitez sauvegarder vos données à l'aide de votre agent de sauvegarde. Le gestionnaire de sauvegarde appellera alors la méthode onBackup() de votre agent de sauvegarde ultérieurement. En règle générale, vous devez demander une sauvegarde chaque fois que vos données changent (par exemple, lorsque l'utilisateur modifie une préférence d'application que vous souhaitez sauvegarder). Si vous appelez dataChanged() plusieurs fois avant que le gestionnaire de sauvegarde ne demande une sauvegarde à votre agent, votre agent ne reçoit quand même qu'un seul appel à onBackup().

Demander une restauration

Au cours du cycle de vie normal de votre application, vous ne devriez pas avoir à demander d'opération de restauration. Le système recherche automatiquement les données de sauvegarde et effectue une restauration lorsque votre application est installée.

Effectuer une migration vers la sauvegarde automatique

Vous pouvez effectuer la transition de votre application vers des sauvegardes complètes de données en définissant android:fullBackupOnly sur true dans l'élément <application> du fichier manifeste. Lorsque vous exécutez l'application sur un appareil équipé d'Android 5.1 (niveau d'API 22) ou version antérieure, votre application ignore cette valeur dans le fichier manifeste et continue à effectuer des sauvegardes clé-valeur. Sur un appareil équipé d'Android 6.0 (niveau d'API 23) ou version ultérieure, votre application effectue une sauvegarde automatique au lieu de la sauvegarde clé-valeur.

Protection de la vie privée des utilisateurs

Chez Google, nous sommes pleinement conscients de la confiance que les utilisateurs nous accordent et de notre responsabilité quant à la protection de leur vie privée. Google transmet de manière sécurisée les données de sauvegarde vers et depuis les serveurs Google afin d'offrir des fonctionnalités de sauvegarde et de restauration. Google traite ces données comme des informations personnelles, conformément à ses Règles de confidentialité.

Par ailleurs, les utilisateurs peuvent désactiver la fonctionnalité de sauvegarde des données via les paramètres de sauvegarde du système Android. Lorsqu'un utilisateur désactive la sauvegarde, Android Backup Service supprime toutes les données de sauvegarde enregistrées. Un utilisateur peut réactiver la sauvegarde sur l'appareil, mais Android Backup Service ne restaurera aucune donnée précédemment supprimée.