Eseguire il backup delle coppie chiave-valore con Android Backup Service

Android Backup Service fornisce backup e ripristino dello spazio di archiviazione sul cloud per i dati delle coppie chiave-valore nella tua app Android. Durante un'operazione di backup delle coppie chiave-valore, i dati di backup dell'app vengono passati al trasporto di backup del dispositivo. Se il dispositivo utilizza il trasporto di backup predefinito di Google, i dati vengono trasmessi ad Android Backup Service per l'archiviazione.

I dati sono limitati a 5 MB per utente dell'app. Non è previsto alcun costo per l'archiviazione dei dati di backup.

Per una panoramica delle opzioni di backup di Android e indicazioni sui dati di cui eseguire il backup e il ripristino, consulta la panoramica sul backup dei dati.

Implementa il backup delle coppie chiave-valore

Per eseguire il backup dei dati dell'app, devi implementare un agente di backup. L'agente di backup viene chiamato dal gestore di backup sia durante il backup che il ripristino.

Per implementare un agente di backup, devi:

  1. Dichiara l'agente di backup nel file manifest con l'attributo android:backupAgent.

  2. Definisci un agente di backup in uno dei seguenti modi:

    • Estensione di BackupAgent

      La classe BackupAgent fornisce l'interfaccia centrale che la tua app utilizza per comunicare con il Gestore backup. Se estendi questa classe direttamente, devi eseguire l'override di onBackup() e onRestore() per gestire le operazioni di backup e ripristino per i tuoi dati.

    • Estensione di BackupAgentHelper

      La classe BackupAgentHelper fornisce un comodo wrapper per la classe BackupAgent, riducendo al minimo la quantità di codice da scrivere. In BackupAgentHelper, devi utilizzare uno o più oggetti helper, che eseguono automaticamente il backup e il ripristino di determinati tipi di dati, in modo da non dover implementare onBackup() e onRestore(). A meno che tu non abbia bisogno del controllo completo sui backup della tua app, ti consigliamo di utilizzare BackupAgentHelper per gestire i backup dell'app.

      Attualmente Android fornisce helper di backup che eseguono il backup e il ripristino dei file completi da SharedPreferences e dallo spazio di archiviazione interno.

Dichiara l'agente di backup nel file manifest

Una volta stabilito il nome della classe per l'agente di backup, dichiaralo nel file manifest utilizzando l'attributo android:backupAgent nel tag <application>.

Ecco alcuni esempi:

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

Per supportare i dispositivi meno recenti, ti consigliamo di aggiungere la chiave API <meta-data> al file manifest di Android. Android Backup Service non richiede più una chiave di servizio, ma alcuni dispositivi meno recenti potrebbero comunque cercare una chiave durante il backup. Imposta android:name su com.google.android.backup.api_key e android:value su unused.

L'attributo android:restoreAnyVersion utilizza un valore booleano per indicare se vuoi ripristinare i dati dell'app a prescindere dalla versione attuale dell'app rispetto alla versione che ha generato i dati di backup. Il valore predefinito è false. Per ulteriori informazioni, consulta Controllare la versione dei dati di ripristino.

Estendi BackupAgentHelper

Devi creare l'agente di backup utilizzando BackupAgentHelper se vuoi eseguire il backup di file completi da SharedPreferences o dalla memoria interna. La creazione dell'agente di backup con BackupAgentHelper richiede molto meno codice rispetto all'estensione di BackupAgent, poiché non è necessario implementare onBackup() e onRestore().

L'implementazione di BackupAgentHelper deve utilizzare uno o più helper di backup. Un helper per il backup è un componente specializzato che viene richiamato da BackupAgentHelper per eseguire operazioni di backup e ripristino per un determinato tipo di dati. Il framework Android al momento offre due diversi strumenti di supporto:

Puoi includere più helper in BackupAgentHelper, ma è sufficiente un solo helper per ogni tipo di dati. In altre parole, se hai più file SharedPreferences, non ti serve altro che SharedPreferencesBackupHelper.

Per ogni helper che vuoi aggiungere a BackupAgentHelper, devi eseguire quanto segue durante il metodo onCreate():

  1. Crea l'istanza di un'istanza della classe helper desiderata. Nel creatore della classe, devi specificare i file di cui vuoi eseguire il backup.
  2. Chiama il numero addHelper() per aggiungere l'helper al tuo BackupAgentHelper.

Le seguenti sezioni descrivono come creare un agente di backup utilizzando ciascuno degli helper disponibili.

Esegui il backup di SharedPreferences

Quando crei un'istanza di SharedPreferencesBackupHelper, devi includere il nome di uno o più file SharedPreferences.

Ad esempio, per eseguire il backup di un file SharedPreferences denominato user_preferences, un agente di backup completo che utilizza BackupAgentHelper ha il seguente aspetto:

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

SharedPreferencesBackupHelper include tutto il codice necessario per eseguire il backup e il ripristino di un file SharedPreferences.

Quando il gestore di backup chiama onBackup() e onRestore(), BackupAgentHelper chiama i tuoi helper di backup per eseguire il backup e il ripristino dei file specificati.

Effettuare il backup di altri file

Quando crei un'istanza di FileBackupHelper, devi includere il nome di uno o più file salvati nella memoria interna dell'app, come specificato da getFilesDir(), che corrisponde alla posizione in cui openFileOutput() scrive i file.

Ad esempio, per eseguire il backup di due file denominati scores e stats, un agente di backup che utilizza BackupAgentHelper ha il seguente aspetto:

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

Il FileBackupHelper include tutto il codice necessario per eseguire il backup e il ripristino dei file salvati nella memoria interna dell'app.

Tuttavia, la lettura e la scrittura nei file nello spazio di archiviazione interno non sono sicure per i thread. Per assicurarti che l'agente di backup non legga o scriva i file contemporaneamente alle attività, devi utilizzare istruzioni sincronizzate ogni volta che esegui una lettura o una scrittura. Ad esempio, in qualsiasi attività in cui leggi e scrivi il file, hai bisogno di un oggetto da utilizzare come blocco intrinseco per le istruzioni sincronizzate:

Kotlin

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

Java

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

Quindi crea un'istruzione sincronizzata con questo blocco ogni volta che leggi o scrivi i file. Ad esempio, ecco un'istruzione sincronizzata per scrivere il punteggio più recente di un gioco in un file:

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

Dovresti sincronizzare le istruzioni lette con lo stesso blocco.

Quindi, nel tuo BackupAgentHelper, devi eseguire l'override di onBackup() e onRestore() per sincronizzare le operazioni di backup e ripristino con lo stesso blocco intrinseco. Ad esempio, l'esempio MyFileBackupAgent sopra riportato richiede i seguenti metodi:

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

Agente di backup esteso

La maggior parte delle app non dovrebbe dover estendere direttamente la classe BackupAgent, ma dovrebbe invece estendere BackupAgentHelper per sfruttare le classi helper integrate che eseguono automaticamente il backup e il ripristino dei file. Tuttavia, potresti estendere BackupAgent direttamente per eseguire le seguenti operazioni:

  • Verifica il formato dei dati. Ad esempio, se prevedi di dover rivedere il formato in cui scrivi i dati dell'app, puoi creare un agente di backup per eseguire un controllo incrociato della versione dell'app durante un'operazione di ripristino ed eseguire eventuali operazioni di compatibilità necessarie se la versione sul dispositivo è diversa da quella dei dati di backup. Per ulteriori informazioni, consulta Controllare la versione dei dati di ripristino.
  • Specifica le parti di dati di cui eseguire il backup. Anziché eseguire il backup di un intero file, puoi specificare le parti di dati di cui eseguire il backup e la modalità di ripristino di ogni parte sul dispositivo. Questo può anche aiutarti a gestire diverse versioni, perché leggi e scrivi i dati come entità univoche, anziché come file completi.
  • Esegui il backup dei dati in un database. Se hai un database SQLite che vuoi ripristinare quando l'utente reinstalla l'app, devi creare un elemento BackupAgent personalizzato che legga i dati appropriati durante un'operazione di backup, quindi creare la tabella e inserire i dati durante un'operazione di ripristino.

Se non devi eseguire nessuna delle attività precedenti e vuoi eseguire il backup dei file completi da SharedPreferences o dalla memoria interna, consulta la sezione Estensione di BackupAgentHelper.

Metodi obbligatori

Quando crei un BackupAgent, devi implementare i seguenti metodi di callback:

onBackup()
Il gestore di backup chiama questo metodo dopo che hai richiesto un backup. Con questo metodo, leggi i dati dell'app dal dispositivo e passi i dati di cui vuoi eseguire il backup a Gestione backup, come descritto nella sezione Esecuzione di un backup.
onRestore()

Il gestore di backup chiama questo metodo durante un'operazione di ripristino. Questo metodo pubblica i dati di backup, che l'app può utilizzare per ripristinare lo stato precedente, come descritto nella sezione Eseguire un ripristino.

Il sistema chiama questo metodo per ripristinare i dati di backup quando l'utente reinstalla la tua app, ma quest'ultima può anche richiedere un ripristino.

Esecuzione di un backup

Una richiesta di backup non comporta una chiamata immediata al tuo metodo onBackup(). Al contrario, il gestore di backup attende un tempo appropriato, quindi esegue un backup per tutte le app che hanno richiesto un backup dall'esecuzione dell'ultimo backup. A questo punto devi fornire i dati dell'app al gestore di backup in modo che possano essere salvati nello spazio di archiviazione sul cloud.

Solo il gestore di backup può chiamare il metodo onBackup() dell'agente di backup. Ogni volta che i dati dell'app cambiano e vuoi eseguire un backup, devi richiedere un'operazione di backup chiamando dataChanged(). Per ulteriori informazioni, vedi Richiedere un backup.

Suggerimento: durante lo sviluppo dell'app, puoi avviare un'operazione di backup immediata da Gestione backup con lo strumento bmgr.

Quando il gestore di backup chiama il metodo onBackup(), trasmette tre parametri:

oldState
Un elemento ParcelFileDescriptor aperto in sola lettura che rimanda all'ultimo stato del backup fornito dall'app. Non si tratta dei dati di backup dello spazio di archiviazione sul cloud, ma di una rappresentazione locale dei dati di cui è stato eseguito il backup l'ultima volta che onBackup() è stato chiamato, come definito da newState o da onRestore(). L'argomento onRestore() è trattato nella sezione successiva. Poiché onBackup() non consente di leggere i dati di backup esistenti nello spazio di archiviazione sul cloud, puoi utilizzare questa rappresentazione locale per determinare se i dati sono stati modificati dall'ultimo backup.
data
Un oggetto BackupDataOutput, che utilizzi per inviare i dati di backup a Gestione backup.
newState
Una ParcelFileDescriptor aperta in lettura/scrittura che rimanda a un file in cui devi scrivere una rappresentazione dei dati che hai consegnato a data. Una rappresentazione può essere semplice come il timestamp dell'ultima modifica del file. Questo oggetto viene restituito come oldState la prossima volta che il gestore di backup chiama il tuo metodo onBackup(). Se non scrivi i dati di backup su newState, oldState punterà a un file vuoto la prossima volta che il gestore di backup chiama onBackup().

Utilizzando questi parametri, implementa il metodo onBackup() per:

  1. Controlla se i dati sono stati modificati dall'ultimo backup confrontando oldState con i dati attuali. La modalità di lettura dei dati in oldState dipende da come li hai scritti originariamente in newState (vedi il passaggio 3). Il modo più semplice per registrare lo stato di un file è tramite il timestamp dell'ultima modifica. Ad esempio, ecco come leggere e confrontare un timestamp di 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 non è cambiato nulla e non devi eseguire il backup, vai al passaggio 3.

  2. Se i dati sono cambiati rispetto a oldState, scrivi i dati attuali in data per eseguirne il backup nello spazio di archiviazione sul cloud.

    Devi scrivere ogni blocco di dati come entità in BackupDataOutput. Un'entità è un record di dati binari suddivisi, identificato da una stringa chiave univoca. Pertanto, il set di dati di cui esegui il backup è concettualmente un insieme di coppie chiave-valore.

    Per aggiungere un'entità al set di dati di backup, devi:

    1. Chiama writeEntityHeader(), passando una chiave stringa univoca per i dati che stai per scrivere e le relative dimensioni.

    2. Richiama writeEntityData(), passando un buffer di byte contenente i tuoi dati e il numero di byte da scrivere dal buffer, che dovrebbe corrispondere alle dimensioni trasmesse a writeEntityHeader().

    Ad esempio, il seguente codice suddivide alcuni dati in un flusso di byte e li scrive in una singola 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);
    

    Esegui questa operazione per ogni dato di cui vuoi eseguire il backup. La suddivisione dei dati in entità dipende da te. Puoi anche utilizzare una sola entità.

  3. A prescindere che tu esegua o meno un backup (nel passaggio 2), scrivi una rappresentazione dei dati attuali nell'ParcelFileDescriptor newState. Il gestore di backup conserva questo oggetto localmente come rappresentazione dei dati di cui viene attualmente eseguito il backup. Ti restituisce il messaggio come oldState la prossima volta che chiama onBackup(), per consentirti di determinare se è necessario un altro backup, come gestito nel passaggio 1. Se non scrivi lo stato dei dati corrente in questo file, oldState sarà vuoto al successivo callback.

    L'esempio seguente salva una rappresentazione dei dati attuali in newState utilizzando il timestamp dell'ultima modifica del file:

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

Esegui un ripristino

Quando è il momento di ripristinare i dati dell'app, il sistema di gestione dei backup chiama il metodo onRestore() dell'agente di backup. Quando chiama questo metodo, il sistema di gestione dei backup fornisce i dati di backup in modo che tu possa ripristinarli sul dispositivo.

Solo il gestore di backup può chiamare onRestore(). Questa operazione si verifica automaticamente quando il sistema installa l'app e trova i dati di backup esistenti.

Quando il gestore di backup chiama il metodo onRestore(), trasmette tre parametri:

data
Un oggetto BackupDataInput, che consente di leggere i dati di backup.
appVersionCode
Un numero intero che rappresenta il valore dell'attributo manifest android:versionCode della tua app, così come lo è stato al momento del backup dei dati. Puoi utilizzarlo per eseguire un controllo incrociato della versione corrente dell'app e determinare se il formato dei dati è compatibile. Per ulteriori informazioni sull'utilizzo di questa funzione per gestire diverse versioni dei dati di ripristino, consulta Controllare la versione dei dati di ripristino.
newState
Una ParcelFileDescriptor aperta in lettura/scrittura che rimanda a un file in cui devi scrivere lo stato finale del backup fornito con data. Questo oggetto viene restituito come oldState alla successiva chiamata di onBackup(). Ricorda che devi anche scrivere lo stesso oggetto newState nel callback onBackup(). In questo modo, inoltre, l'oggetto oldState assegnato a onBackup() sarà valido anche la prima volta che onBackup() viene chiamato dopo il ripristino del dispositivo.

Nella tua implementazione di onRestore(), devi chiamare readNextHeader() in data per eseguire l'iterazione in tutte le entità del set di dati. Per ogni entità rilevata, procedi nel seguente modo:

  1. Ottieni la chiave entità con getKey().
  2. Confronta la chiave dell'entità con un elenco di valori chiave noti che dovresti aver dichiarato come stringhe finali statiche all'interno della classe BackupAgent. Quando la chiave corrisponde a una delle stringhe di chiave note, inserisci un'istruzione per estrarre i dati dell'entità e salvali sul dispositivo:

    1. Ottieni la dimensione dei dati dell'entità con getDataSize() e crea un array di byte di questa dimensione.
    2. Chiama readEntityData() e passa l'array di byte, ovvero il punto in cui verranno inseriti i dati, e specifica l'offset iniziale e la dimensione da leggere.
    3. Il tuo array di byte è ora pieno. Leggi i dati e scrivili sul dispositivo come preferisci.
  3. Dopo aver letto e scritto i dati sul dispositivo, scrivi lo stato dei dati nel parametro newState come hai fatto durante onBackup().

Ad esempio, ecco come ripristinare i dati di cui è stato eseguito il backup nell'esempio riportato nella sezione precedente:

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

In questo esempio, il parametro appVersionCode passato a onRestore() non viene utilizzato. Tuttavia, ti consigliamo di utilizzarlo se hai scelto di eseguire un backup quando la versione dell'app dell'utente è effettivamente stata spostata indietro (ad esempio, l'utente è passato dalla versione 1.5 dell'app alla versione 1.0). Per ulteriori informazioni, consulta la sezione successiva.

Controlla la versione dei dati di ripristino

Quando il gestore di backup salva i dati nello spazio di archiviazione sul cloud, include automaticamente la versione dell'app, come definito dall'attributo android:versionCode del file manifest. Prima di chiamare l'agente di backup per ripristinare i dati, Gestione backup esamina il valore android:versionCode dell'app installata e lo confronta con il valore registrato nel set di dati di ripristino. Se la versione registrata nel set di dati di ripristino è più recente rispetto alla versione dell'app sul dispositivo, l'utente ha eseguito il downgrade dell'app. In questo caso, il gestore di backup interromperà l'operazione di ripristino dell'app e non chiamerà il metodo onRestore(), perché il set di ripristino è considerato privo di significato per una versione precedente.

Puoi ignorare questo comportamento con l'attributo android:restoreAnyVersion. Imposta questo attributo su true per indicare che vuoi ripristinare l'app indipendentemente dalla versione del set di ripristino. Il valore predefinito è false. Se imposti questo metodo su true, il gestore dei backup ignorerà il android:versionCode e chiamerà il tuo metodo onRestore() in tutti i casi. In questo modo, puoi controllare manualmente la differenza di versione nel tuo metodo onRestore() ed eseguire le operazioni necessarie per rendere i dati compatibili se le versioni non corrispondono.

Per aiutarti a gestire versioni diverse durante un'operazione di ripristino, il metodo onRestore() ti passa il codice di versione incluso nel set di dati di ripristino come parametro appVersionCode. Puoi quindi eseguire query sul codice di versione attuale dell'app con il campo PackageInfo.versionCode. Ecco alcuni esempi:

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

Poi confronta il valore version acquisito da PackageInfo con il valore appVersionCode passato in onRestore().

Richiedi un backup

Puoi richiedere un'operazione di backup in qualsiasi momento chiamando il numero dataChanged(). Questo metodo comunica al gestore di backup che vuoi eseguire il backup dei dati utilizzando l'agente di backup. In futuro, il gestore di backup chiama il metodo onBackup() dell'agente di backup. In genere, è consigliabile richiedere un backup ogni volta che i dati cambiano (ad esempio quando l'utente cambia la preferenza di un'app di cui vuoi eseguire il backup). Se chiami dataChanged() diverse volte prima che il gestore di backup richieda un backup all'agente, quest'ultimo riceve comunque una sola chiamata al numero onBackup().

Richiedi un ripristino

Durante la normale vita dell'app, non dovresti richiedere un'operazione di ripristino. Il sistema verifica automaticamente la presenza di dati di backup ed esegue un ripristino quando l'app viene installata.

Esegui la migrazione a Backup automatico

Puoi eseguire la transizione dell'app ai backup dei dati completi impostando android:fullBackupOnly su true nell'elemento <application> del file manifest. Quando viene eseguita su un dispositivo con Android 5.1 (livello API 22) o versioni precedenti, l'app ignora questo valore nel manifest e continua a eseguire backup delle coppie chiave-valore. Quando è eseguita su un dispositivo con Android 6.0 (livello API 23) o versioni successive, l'app esegue il backup automatico anziché il backup delle coppie chiave-valore.

Privacy degli utenti

Google è consapevole della fiducia che gli utenti ripongono in noi e della nostra responsabilità di proteggere la privacy degli utenti. Google trasmette in modo sicuro i dati di backup da e verso i propri server per fornire funzionalità di backup e ripristino. Google tratta questi dati come informazioni personali in conformità con le Norme sulla privacy di Google.

Inoltre, gli utenti possono disattivare la funzionalità di backup dei dati tramite le impostazioni di backup del sistema Android. Quando un utente disattiva il backup, Android Backup Service elimina tutti i dati di backup salvati. Un utente può riattivare il backup sul dispositivo, ma Android Backup Service non ripristinerà i dati eliminati in precedenza.