Schlüssel/Wert-Paare mit dem Android Backup Service sichern

Der Android-Sicherungsdienst bietet Cloud-Speicher-Sicherung und -Wiederherstellung für Schlüssel/Wert-Daten in Ihrer Android-App. Während eines Schlüssel/Wert-Sicherungsvorgangs werden die Sicherungsdaten der App an den Sicherungstransport des Geräts übergeben. Wenn das Gerät den standardmäßigen Google Backup-Transferdienst verwendet, werden die Daten zur Archivierung an den Android Backup Service übergeben.

Die Daten sind auf 5 MB pro Nutzer Ihrer App beschränkt. Für das Speichern von Sicherungsdaten fallen keine Gebühren an.

Eine Übersicht über die Sicherungsoptionen von Android und eine Anleitung dazu, welche Daten gesichert und wiederhergestellt werden sollten, findest du in der Übersicht über die Datensicherung.

Schlüssel/Wert-Sicherung implementieren

Wenn Sie Ihre App-Daten sichern möchten, müssen Sie einen Sicherungs-Agent implementieren. Der Sicherungs-Agent wird vom Backup Manager sowohl während der Sicherung als auch während der Wiederherstellung aufgerufen.

So implementieren Sie einen Backup-Agenten:

  1. Deklariere deinen Sicherungs-Agent in deiner Manifestdatei mit dem Attribut android:backupAgent.

  2. Definieren Sie einen Sicherungs-Agenten mit einer der folgenden Methoden:

    • BackupAgent wird verlängert

      Die Klasse BackupAgent stellt die zentrale Schnittstelle bereit, über die Ihre App mit dem Sicherungsmanager kommuniziert. Wenn Sie diese Klasse direkt erweitern, müssen Sie onBackup() und onRestore() überschreiben, um die Sicherungs- und Wiederherstellungsvorgänge für Ihre Daten zu verarbeiten.

    • BackupAgentHelper wird verlängert

      Die Klasse BackupAgentHelper bietet einen praktischen Wrapper für die Klasse BackupAgent, wodurch die Menge an Code, die Sie schreiben müssen, minimiert wird. In Ihrer BackupAgentHelper müssen Sie ein oder mehrere Hilfsobjekte verwenden, die bestimmte Datentypen automatisch sichern und wiederherstellen, damit Sie onBackup() und onRestore() nicht implementieren müssen. Wenn Sie nicht die volle Kontrolle über die Sicherungen Ihrer App benötigen, empfehlen wir, die Sicherungen Ihrer App mit BackupAgentHelper zu verwalten.

      Android bietet derzeit Sicherungshelfer, die vollständige Dateien aus SharedPreferences und dem internen Speicher sichern und wiederherstellen.

Backup-Agent im Manifest deklarieren

Nachdem du dich für den Klassennamen deines Sicherungs-Agents entschieden hast, deklariere ihn in deinem Manifest mit dem Attribut android:backupAgent im Tag <application>.

Beispiel:

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

Damit ältere Geräte unterstützt werden, empfehlen wir, den API-Schlüssel <meta-data> in Ihre Android-Manifestdatei einzufügen. Für den Android Backup Service ist kein Dienstschlüssel mehr erforderlich. Bei einigen älteren Geräten wird jedoch möglicherweise weiterhin nach einem Schlüssel gesucht, wenn eine Sicherung durchgeführt wird. Setzen Sie android:name auf com.google.android.backup.api_key und android:value auf unused.

Das Attribut android:restoreAnyVersion akzeptiert einen booleschen Wert, der angibt, ob Sie die App-Daten unabhängig von der aktuellen App-Version im Vergleich zu der Version, die die Sicherungsdaten erzeugt hat, wiederherstellen möchten. Der Standardwert ist false. Weitere Informationen finden Sie unter Version der Wiederherstellungsdaten prüfen.

BackupAgentHelper erweitern

Sie sollten Ihren Sicherungs-Agent mit BackupAgentHelper erstellen, wenn Sie vollständige Dateien von SharedPreferences oder dem internen Speicher sichern möchten. Wenn Sie Ihren Sicherungs-Agent mit BackupAgentHelper erstellen, ist viel weniger Code erforderlich als beim Erweitern von BackupAgent, da Sie onBackup() und onRestore() nicht implementieren müssen.

Ihre Implementierung von BackupAgentHelper muss mindestens einen Sicherungs-Helper verwenden. Ein Sicherungs-Helper ist eine spezielle Komponente, die BackupAgentHelper aufruft, um Sicherungs- und Wiederherstellungsvorgänge für einen bestimmten Datentyp auszuführen. Das Android-Framework bietet derzeit zwei verschiedene Hilfsprogramme:

Du kannst mehrere Hilfsprogramme in deine BackupAgentHelper aufnehmen, aber für jeden Datentyp ist nur ein Hilfsprogramm erforderlich. Wenn Sie also mehrere SharedPreferences-Dateien haben, benötigen Sie nur eine SharedPreferencesBackupHelper.

Für jeden Helfer, den Sie Ihrem BackupAgentHelper hinzufügen möchten, müssen Sie in der Methode onCreate() Folgendes tun:

  1. Instanziieren Sie eine Instanz der ausgewählten Hilfsklasse. Im Klassenkonstruktor müssen Sie die Dateien angeben, die Sie sichern möchten.
  2. Rufen Sie addHelper() auf, um den Helfer zu BackupAgentHelper hinzuzufügen.

In den folgenden Abschnitten wird beschrieben, wie Sie einen Sicherungs-Agenten mit den verfügbaren Hilfsprogrammen erstellen.

SharedPreferences sichern

Wenn Sie eine SharedPreferencesBackupHelper instanziieren, müssen Sie den Namen einer oder mehrerer SharedPreferences-Dateien angeben.

Wenn Sie beispielsweise eine SharedPreferences-Datei mit dem Namen user_preferences sichern möchten, sieht ein vollständiger Sicherungs-Agent mit BackupAgentHelper so aus:

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

Die SharedPreferencesBackupHelper enthält den gesamten Code, der zum Sichern und Wiederherstellen einer SharedPreferences-Datei erforderlich ist.

Wenn der Backup Manager onBackup() und onRestore() aufruft, ruft BackupAgentHelper Ihre Sicherungs-Hilfsprogramme auf, um die angegebenen Dateien zu sichern und wiederherzustellen.

Andere Dateien sichern

Wenn Sie eine FileBackupHelper instanziieren, müssen Sie den Namen einer oder mehrerer Dateien angeben, die im internen Speicher Ihrer App gespeichert sind, wie durch getFilesDir() angegeben. Dies ist derselbe Speicherort, an dem openFileOutput() Dateien schreibt.

Wenn Sie beispielsweise zwei Dateien mit den Namen scores und stats sichern möchten, sieht ein Sicherungs-Agent, der BackupAgentHelper verwendet, so aus:

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

Die FileBackupHelper enthält den gesamten Code, der zum Sichern und Wiederherstellen von Dateien erforderlich ist, die im internen Speicher Ihrer App gespeichert sind.

Das Lesen und Schreiben von Dateien im internen Speicher ist jedoch nicht threadsicher. Damit Ihr Sicherungs-Agent Ihre Dateien nicht gleichzeitig mit Ihren Aktivitäten liest oder schreibt, müssen Sie bei jedem Lese- oder Schreibvorgang synchronisierte Anweisungen verwenden. In jeder Aktivität, in der Sie die Datei lesen und schreiben, benötigen Sie beispielsweise ein Objekt, das als intrinsische Sperre für die synchronisierten Anweisungen verwendet wird:

Kotlin

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

Java

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

Erstellen Sie dann jedes Mal, wenn Sie die Dateien lesen oder schreiben, eine synchronisierte Anweisung mit dieser Sperre. Hier ist beispielsweise eine synchronisierte Anweisung, um den neuesten Spielstand in eine Datei zu schreiben:

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

Sie sollten Ihre Leseanweisungen mit derselben Sperre synchronisieren.

Anschließend müssen Sie in Ihrer BackupAgentHelper die Methoden onBackup() und onRestore() überschreiben, um die Sicherungs- und Wiederherstellungsvorgänge mit derselben intrinsischen Sperre zu synchronisieren. Für das vorherige MyFileBackupAgent-Beispiel sind beispielsweise die folgenden Methoden erforderlich:

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

BackupAgent erweitern

Bei den meisten Apps ist es nicht erforderlich, die Klasse BackupAgent direkt zu erweitern. Stattdessen sollte BackupAgentHelper erweitert werden, um die integrierten Hilfsklassen zu nutzen, die Ihre Dateien automatisch sichern und wiederherstellen. Sie können BackupAgent jedoch direkt erweitern, um Folgendes zu tun:

  • Versionieren Sie Ihr Datenformat. Wenn Sie beispielsweise davon ausgehen, dass Sie das Format, in dem Sie Ihre App-Daten schreiben, überarbeiten müssen, können Sie einen Sicherungs-Agent erstellen, der die App-Version während eines Wiederherstellungsvorgangs prüft und alle erforderlichen Kompatibilitätsarbeiten ausführt, wenn sich die Version auf dem Gerät von der der Sicherungsdaten unterscheidet. Weitere Informationen finden Sie unter Version der wiederhergestellten Daten prüfen.
  • Geben Sie die zu sichernden Daten an. Anstatt eine ganze Datei zu sichern, können Sie die zu sichernden Datenbereiche und die Art und Weise angeben, wie die einzelnen Bereiche auf dem Gerät wiederhergestellt werden. Dies kann auch bei der Verwaltung verschiedener Versionen helfen, da Sie Ihre Daten als eindeutige Entitäten und nicht als vollständige Dateien lesen und schreiben.
  • Daten in einer Datenbank sichern Wenn Sie eine SQLite-Datenbank haben, die Sie wiederherstellen möchten, wenn der Nutzer Ihre App neu installiert, müssen Sie einen benutzerdefinierten BackupAgent erstellen, der die entsprechenden Daten während eines Sicherungsvorgangs liest. Erstellen Sie dann Ihre Tabelle und fügen Sie die Daten während eines Wiederherstellungsvorgangs ein.

Wenn Sie keine der vorherigen Aufgaben ausführen müssen und vollständige Dateien aus SharedPreferences oder dem internen Speicher sichern möchten, lesen Sie den Abschnitt BackupAgentHelper erweitern.

Erforderliche Methoden

Wenn Sie einen BackupAgent erstellen, müssen Sie die folgenden Callback-Methoden implementieren:

onBackup()
Der Backup Manager ruft diese Methode auf, nachdem du eine Sicherung angefordert hast. Bei dieser Methode liest du deine App-Daten vom Gerät und übergibst die Daten, die du sichern möchtest, an den Backup Manager, wie unter Sicherung durchführen beschrieben.
onRestore()

Der Backup Manager ruft diese Methode während eines Wiederherstellungsvorgangs auf. Diese Methode liefert die Sicherungsdaten, mit denen die App den vorherigen Zustand wiederherstellen kann, wie unter Wiederherstellung durchführen beschrieben.

Das System ruft diese Methode auf, um Sicherungsdaten wiederherzustellen, wenn der Nutzer die App neu installiert. Die App kann aber auch eine Wiederherstellung anfordern.

Sicherung durchführen

Eine Sicherungsanfrage führt nicht zu einem sofortigen Aufruf der Methode onBackup(). Stattdessen wartet der Backup Manager auf einen geeigneten Zeitpunkt und führt dann eine Sicherung für alle Apps durch, für die seit der letzten Sicherung eine Sicherung angefordert wurde. An diesem Punkt müssen Sie Ihre App-Daten für den Backup Manager bereitstellen, damit sie im Cloud-Speicher gespeichert werden können.

Nur der Backup Manager kann die Methode onBackup() des Sicherungs-Agents aufrufen. Jedes Mal, wenn sich Ihre App-Daten ändern und Sie eine Sicherung durchführen möchten, müssen Sie einen Sicherungsvorgang anfordern, indem Sie dataChanged() aufrufen. Weitere Informationen finden Sie unter Sicherung anfordern.

Tipp: Während der Entwicklung deiner App kannst du mit dem bmgr-Tool im Backup Manager einen sofortigen Sicherungsvorgang starten.

Wenn der Backup Manager die Methode onBackup() aufruft, übergibt er drei Parameter:

oldState
Eine offene, schreibgeschützte ParcelFileDescriptor, die auf den letzten Sicherungsstatus verweist, der von Ihrer App bereitgestellt wurde. Dies sind nicht die Sicherungsdaten aus dem Cloud-Speicher, sondern eine lokale Darstellung der Daten, die beim letzten Aufruf von onBackup() gesichert wurden, wie durch newState oder von onRestore() definiert. onRestore() wird im nächsten Abschnitt beschrieben. Da Sie mit onBackup() keine vorhandenen Sicherungsdaten im Cloud-Speicher lesen können, können Sie anhand dieser lokalen Darstellung feststellen, ob sich Ihre Daten seit der letzten Sicherung geändert haben.
data
Ein BackupDataOutput-Objekt, mit dem Sie Ihre Sicherungsdaten an den Backup Manager übermitteln.
newState
Eine geöffnete ParcelFileDescriptor mit Lese- und Schreibzugriff, die auf eine Datei verweist, in die Sie eine Darstellung der Daten schreiben müssen, die Sie an data übergeben haben. Eine Darstellung kann der Zeitstempel der letzten Änderung Ihrer Datei sein. Dieses Objekt wird als oldState zurückgegeben, wenn der Sicherungsmanager das nächste Mal die Methode onBackup() aufruft. Wenn Sie Ihre Sicherungsdaten nicht in newState schreiben, verweist oldState beim nächsten Aufruf von onBackup() durch den Sicherungsmanager auf eine leere Datei.

Implementieren Sie mit diesen Parametern die Methode onBackup(), um Folgendes zu tun:

  1. Prüfen Sie, ob sich Ihre Daten seit der letzten Sicherung geändert haben, indem Sie oldState mit Ihren aktuellen Daten vergleichen. Wie Sie Daten in oldState lesen, hängt davon ab, wie Sie sie ursprünglich in newState geschrieben haben (siehe Schritt 3). Am einfachsten lässt sich der Status einer Datei anhand des Zeitstempels der letzten Änderung erfassen. So können Sie beispielsweise einen Zeitstempel aus oldState lesen und vergleichen:

    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
    }
    

    Wenn sich nichts geändert hat und Sie keine Sicherung benötigen, fahren Sie mit Schritt 3 fort.

  2. Wenn sich Ihre Daten im Vergleich zu oldState geändert haben, schreiben Sie die aktuellen Daten in data, um sie im Cloud-Speicher zu sichern.

    Sie müssen jeden Datenblock als Entität in BackupDataOutput schreiben. Eine Entität ist ein vereinfachter binärer Datensatz, der durch einen eindeutigen Schlüsselstring identifiziert wird. Das Dataset, das Sie sichern, ist also konzeptionell eine Reihe von Schlüssel/Wert-Paaren.

    So fügen Sie Ihrem Sicherungs-Dataset eine Entität hinzu:

    1. Rufe writeEntityHeader() auf und übergib einen eindeutigen Stringschlüssel für die Daten, die du schreiben möchtest, sowie die Datengröße.

    2. Rufen Sie writeEntityData() auf und übergeben Sie einen Bytepuffer, der Ihre Daten enthält, sowie die Anzahl der Byte, die aus dem Puffer geschrieben werden sollen. Diese Anzahl sollte mit der Größe übereinstimmen, die an writeEntityHeader() übergeben wurde.

    Im folgenden Code werden beispielsweise einige Daten in einen Bytestream vereinfacht und in eine einzelne Entität geschrieben:

    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 using the BackupDataOutput
    byte[] buffer = bufStream.toByteArray();
    int len = buffer.length;
    data.writeEntityHeader(TOPSCORE_BACKUP_KEY, len);
    data.writeEntityData(buffer, len);
    

    Wiederholen Sie diesen Schritt für alle Daten, die Sie sichern möchten. Wie Sie Ihre Daten in Entitäten aufteilen, bleibt Ihnen überlassen. Möglicherweise verwenden Sie sogar nur eine Entität.

  3. Unabhängig davon, ob Sie eine Sicherung erstellen (in Schritt 2), schreiben Sie eine Darstellung der aktuellen Daten in die ParcelFileDescriptor newState. Der Backup Manager speichert dieses Objekt lokal als Darstellung der aktuell gesicherten Daten. Dieser wird beim nächsten Aufruf von onBackup() als oldState an Sie zurückgegeben, damit Sie feststellen können, ob eine weitere Sicherung erforderlich ist, wie in Schritt 1 beschrieben. Wenn Sie den aktuellen Datenstatus nicht in diese Datei schreiben, ist oldState beim nächsten Callback leer.

    Im folgenden Beispiel wird eine Darstellung der aktuellen Daten in newState gespeichert. Dabei wird der Zeitstempel der letzten Änderung der Datei verwendet:

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

Wiederherstellung durchführen

Wenn es an der Zeit ist, deine App-Daten wiederherzustellen, ruft der Sicherungsmanager die Methode onRestore() deines Sicherungs-Agents auf. Wenn diese Methode aufgerufen wird, liefert der Sicherungsmanager die Sicherungsdaten, damit du sie auf dem Gerät wiederherstellen kannst.

Nur der Sicherungsmanager kann onRestore() aufrufen. Dies geschieht automatisch, wenn das System Ihre App installiert und vorhandene Sicherungsdaten findet.

Wenn der Backup Manager die Methode onRestore() aufruft, übergibt er drei Parameter:

data
Ein BackupDataInput-Objekt, mit dem Sie Ihre Sicherungsdaten lesen können.
appVersionCode
Eine Ganzzahl, die den Wert des Manifestattributs android:versionCode Ihrer App zum Zeitpunkt der Sicherung dieser Daten darstellt. Damit können Sie die aktuelle App-Version prüfen und feststellen, ob das Datenformat kompatibel ist. Weitere Informationen zur Verwendung dieser Funktion für die Verarbeitung verschiedener Versionen von Wiederherstellungsdaten finden Sie unter Version der Wiederherstellungsdaten prüfen.
newState
Eine geöffnete ParcelFileDescriptor mit Lese-/Schreibzugriff, die auf eine Datei verweist, in die Sie den endgültigen Sicherungsstatus schreiben müssen, der mit data bereitgestellt wurde. Dieses Objekt wird beim nächsten Aufruf von onBackup() als oldState zurückgegeben. Denken Sie daran, dass Sie dasselbe newState-Objekt auch im onBackup()-Callback schreiben müssen. Wenn Sie es auch hier tun, ist das oldState-Objekt, das an onBackup() übergeben wird, auch beim ersten Aufruf von onBackup() nach der Wiederherstellung des Geräts gültig.

In Ihrer Implementierung von onRestore() sollten Sie readNextHeader() für data aufrufen, um alle Entitäten im Dataset zu durchlaufen. Führen Sie für jede gefundene Entität die folgenden Schritte aus:

  1. Rufen Sie den Entitätsschlüssel mit getKey() ab.
  2. Vergleichen Sie den Entitätsschlüssel mit einer Liste bekannter Schlüsselwerte, die Sie in Ihrer BackupAgent-Klasse als statische finale Strings deklariert haben sollten. Wenn der Schlüssel mit einem Ihrer bekannten Schlüsselstrings übereinstimmt, geben Sie eine Anweisung ein, um die Entitätsdaten zu extrahieren und auf dem Gerät zu speichern:

    1. Rufen Sie die Größe der Entitätsdaten mit getDataSize() ab und erstellen Sie ein Byte-Array dieser Größe.
    2. Rufen Sie readEntityData() auf und übergeben Sie das Byte-Array, in das die Daten geschrieben werden sollen. Geben Sie außerdem den Start-Offset und die zu lesende Größe an.
    3. Das Byte-Array ist jetzt voll. Lesen Sie die Daten und schreiben Sie sie nach Belieben auf das Gerät.
  3. Nachdem Sie Ihre Daten gelesen und wieder auf das Gerät geschrieben haben, schreiben Sie den Status Ihrer Daten in den Parameter newState, so wie Sie es auch während onBackup() tun.

So können Sie beispielsweise die Daten wiederherstellen, die im Beispiel im vorherigen Abschnitt gesichert wurden:

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 diesem Beispiel wird der an onRestore() übergebene Parameter appVersionCode nicht verwendet. Sie können sie jedoch verwenden, wenn Sie eine Sicherung durchführen möchten, wenn die Version der App des Nutzers tatsächlich zurückgesetzt wurde (z. B. wenn der Nutzer von Version 1.5 Ihrer App zu Version 1.0 gewechselt ist). Weitere Informationen finden Sie im nächsten Abschnitt.

Version der wiederhergestellten Daten prüfen

Wenn der Backup Manager Ihre Daten im Cloud-Speicher speichert, wird automatisch die Version Ihrer App einbezogen, die durch das Attribut android:versionCode Ihrer Manifestdatei definiert ist. Bevor der Backup Manager den Sicherungs-Agent aufruft, um Ihre Daten wiederherzustellen, prüft er den android:versionCode der installierten App und vergleicht ihn mit dem Wert im Wiederherstellungs-Dataset. Wenn die im Wiederherstellungs-Dataset aufgezeichnete Version neuer ist als die App-Version auf dem Gerät, hat der Nutzer ein Downgrade seiner App durchgeführt. In diesem Fall bricht der Sicherungsmanager den Wiederherstellungsvorgang für deine App ab und ruft deine onRestore()-Methode nicht auf, da der Wiederherstellungssatz für eine ältere Version als bedeutungslos gilt.

Sie können dieses Verhalten mit dem Attribut android:restoreAnyVersion überschreiben:

  • false (Standard): Der Backup Manager bricht den Wiederherstellungsvorgang ab, wenn die im Wiederherstellungs-Dataset aufgezeichnete Version neuer ist als die App-Version auf dem Gerät.
  • true: Der Backup Manager ignoriert die android:versionCode und ruft in allen Fällen die Methode onRestore() auf, sodass Sie manuell nach Versionsunterschieden suchen und die Daten kompatibel machen können.

Damit Sie während eines Wiederherstellungsvorgangs verschiedene Versionen verarbeiten können, übergibt die Methode onRestore() den Versionscode, der im Wiederherstellungs-Dataset enthalten ist, als Parameter appVersionCode. Anschließend können Sie den Versionscode der aktuellen App mit dem Feld PackageInfo.versionCode abfragen. Beispiel:

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

Vergleichen Sie dann die von PackageInfo abgerufene version mit der an onRestore() übergebenen appVersionCode.

Sicherung anfordern

Sie können jederzeit einen Sicherungsvorgang anfordern, indem Sie dataChanged() aufrufen. Mit dieser Methode wird der Sicherungsmanager darüber informiert, dass Sie Ihre Daten mit Ihrem Sicherungs-Agent sichern möchten. Der Backup Manager ruft dann zu einem späteren Zeitpunkt die Methode onBackup() des Sicherungs-Agents auf. In der Regel solltest du jedes Mal eine Sicherung anfordern, wenn sich deine Daten ändern, z. B. wenn der Nutzer eine App-Einstellung ändert, die du sichern möchtest. Wenn Sie dataChanged() mehrmals aufrufen, bevor der Backup Manager eine Sicherung von Ihrem Agenten anfordert, erhält Ihr Agent trotzdem nur einen Aufruf von onBackup().

Wiederherstellung anfordern

Während der normalen Lebensdauer Ihrer App sollte es nicht erforderlich sein, einen Wiederherstellungsvorgang anzufordern. Das System sucht automatisch nach Sicherungsdaten und führt eine Wiederherstellung durch, wenn deine App installiert wird.

Zur automatischen Sicherung migrieren

Sie können Ihre App auf vollständige Datensicherungen umstellen, indem Sie android:fullBackupOnly im Element <application> in der Manifestdatei auf true setzen. Wenn Ihre App auf einem Gerät mit Android 5.1 (API-Level 22) oder niedriger ausgeführt wird, ignoriert sie diesen Wert im Manifest und führt weiterhin Schlüssel/Wert-Sicherungen durch. Wenn Ihre App auf einem Gerät mit Android 6.0 (API-Level 23) oder höher ausgeführt wird, wird die automatische Sicherung anstelle der Schlüssel/Wert-Sicherung verwendet.

Datenschutz (für Nutzer)

Wir bei Google schätzen das Vertrauen, das Nutzer uns entgegenbringen, und sind uns unserer Verantwortung bewusst, ihre Daten zu schützen. Google überträgt Sicherungsdaten sicher an und von Google-Servern, um Sicherungs- und Wiederherstellungsfunktionen bereitzustellen. Diese Informationen werden von Google gemäß der Datenschutzerklärung von Google als personenbezogene Daten behandelt.

Außerdem können Nutzer die Datensicherung in den Sicherungseinstellungen des Android-Systems deaktivieren. Wenn ein Nutzer die Sicherung deaktiviert, löscht der Android Backup Service alle gespeicherten Sicherungsdaten. Ein Nutzer kann die Sicherung auf dem Gerät wieder aktivieren, aber Android Backup Service stellt keine zuvor gelöschten Daten wieder her.