Tworzenie kopii zapasowych par klucz-wartość przy użyciu Android Backup Service

Android Backup Service umożliwia tworzenie kopii zapasowej w miejscu w chmurze i przywracanie danych klucz-wartość w aplikacji na Androida. Podczas operacji tworzenia kopii zapasowej danych klucz-wartość dane kopii zapasowej aplikacji są przekazywane do transportu kopii zapasowej urządzenia. Jeśli urządzenie korzysta z domyślnego przesyłania kopii zapasowej Google, dane są przekazywane do Android Backup Service w celu archiwizacji.

Ilość danych jest ograniczona do 5 MB na użytkownika aplikacji. Przechowywanie kopii zapasowych danych jest bezpłatne.

Omówienie opcji tworzenia kopii zapasowych na Androidzie oraz wskazówki dotyczące tego, które dane należy uwzględnić w kopii zapasowej i przywrócić, znajdziesz w artykule Omówienie tworzenia kopii zapasowych danych.

Wdrażanie kopii zapasowej par klucz-wartość

Aby utworzyć kopię zapasową danych aplikacji, musisz zaimplementować agenta kopii zapasowej. Agent kopii zapasowej jest wywoływany przez menedżera kopii zapasowych zarówno podczas tworzenia, jak i przywracania kopii zapasowej.

Aby wdrożyć agenta kopii zapasowej, musisz:

  1. Zadeklaruj agenta kopii zapasowej w pliku manifestu za pomocą atrybutu android:backupAgent.

  2. Zdefiniuj agenta kopii zapasowej, wykonując jedną z tych czynności:

    • Przedłużanie: BackupAgent

      Klasa BackupAgent udostępnia centralny interfejs, którego aplikacja używa do komunikacji z Menedżerem kopii zapasowych. Jeśli rozszerzysz tę klasę bezpośrednio, musisz zastąpić metody onBackup() i onRestore(), aby obsługiwać operacje tworzenia i przywracania kopii zapasowej danych.

    • Przedłużam: BackupAgentHelper

      Klasa BackupAgentHelper zapewnia wygodną otoczkę wokół klasy BackupAgent, co minimalizuje ilość kodu, który musisz napisać. W BackupAgentHelper musisz użyć co najmniej 1 obiektu pomocniczego, który automatycznie tworzy kopie zapasowe i przywraca określone typy danych, dzięki czemu nie musisz implementować onBackup() i onRestore(). Jeśli nie potrzebujesz pełnej kontroli nad kopiami zapasowymi aplikacji, zalecamy używanie BackupAgentHelper do ich obsługi.

      Android udostępnia obecnie pomocników kopii zapasowej, którzy tworzą kopie zapasowe i przywracają całe pliki z SharedPreferences i pamięci wewnętrznej.

Deklarowanie agenta kopii zapasowej w pliku manifestu

Po wybraniu nazwy klasy agenta kopii zapasowej zadeklaruj ją w pliku manifestu za pomocą atrybutu android:backupAgent w tagu <application>.

Przykład:

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

Aby zapewnić obsługę starszych urządzeń, zalecamy dodanie klucza interfejsu API <meta-data> do pliku manifestu Androida. Usługa Android Backup Service nie wymaga już klucza usługi, ale niektóre starsze urządzenia mogą nadal sprawdzać klucz podczas tworzenia kopii zapasowej. Ustaw android:name na com.google.android.backup.api_key, a android:value na unused.

Atrybut android:restoreAnyVersion przyjmuje wartość logiczną, która wskazuje, czy chcesz przywrócić dane aplikacji niezależnie od bieżącej wersji aplikacji w porównaniu z wersją, która utworzyła dane kopii zapasowej. Wartością domyślną jest false. Więcej informacji znajdziesz w sekcji Sprawdzanie wersji danych do przywrócenia.

Rozszerzanie klasy BackupAgentHelper

Jeśli chcesz utworzyć kopię zapasową całych plików z SharedPreferences lub pamięci wewnętrznej, utwórz agenta kopii zapasowej za pomocą BackupAgentHelper. Tworzenie agenta kopii zapasowej za pomocą BackupAgentHelper wymaga znacznie mniej kodu niż rozszerzenie BackupAgent, ponieważ nie musisz implementować onBackup() i onRestore().

Twoja implementacja BackupAgentHelper musi używać co najmniej 1 pomocnika kopii zapasowej. Pomocnik kopii zapasowej to wyspecjalizowany komponent, który BackupAgentHelper wywołuje, aby wykonywać operacje tworzenia i przywracania kopii zapasowej określonego typu danych. Platforma Androida udostępnia obecnie 2 różne klasy pomocnicze:

W aplikacji BackupAgentHelper możesz uwzględnić wiele obiektów pomocniczych, ale dla każdego typu danych potrzebny jest tylko jeden obiekt pomocniczy. Jeśli masz kilka plików SharedPreferences, potrzebujesz tylko jednego pliku SharedPreferencesBackupHelper.

W przypadku każdego pomocnika, którego chcesz dodać do BackupAgentHelper, musisz wykonać te czynności podczas metody onCreate():

  1. Utwórz instancję wybranej klasy pomocniczej. W konstruktorze klasy musisz określić pliki, których kopię zapasową chcesz utworzyć.
  2. Zadzwoń pod numer addHelper(), aby dodać pomocnika do swojego urządzenia BackupAgentHelper.

W sekcjach poniżej opisano, jak utworzyć agenta kopii zapasowej za pomocą każdego z dostępnych pomocników.

Tworzenie kopii zapasowej SharedPreferences

Podczas tworzenia instancji SharedPreferencesBackupHelper musisz podać nazwę co najmniej 1 pliku SharedPreferences.

Na przykład, aby utworzyć kopię zapasową pliku SharedPreferences o nazwie user_preferences, kompletny agent kopii zapasowej korzystający z BackupAgentHelper wygląda tak:

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 zawiera cały kod potrzebny do utworzenia kopii zapasowej i przywrócenia pliku SharedPreferences.

Gdy Menedżer kopii zapasowych wywołuje onBackup() i onRestore(), BackupAgentHelper wywołuje pomocników kopii zapasowej, aby utworzyć i przywrócić kopię zapasową określonych plików.

Tworzenie kopii zapasowej innych plików

Podczas tworzenia instancji FileBackupHelper musisz podać nazwę co najmniej 1 pliku zapisanego w pamięci wewnętrznej aplikacji, zgodnie z getFilesDir(), czyli w tej samej lokalizacji, w której openFileOutput() zapisuje pliki.

Na przykład, aby utworzyć kopię zapasową 2 plików o nazwach scores i stats, agent kopii zapasowej korzystający z BackupAgentHelper wygląda tak:

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

FileBackupHelper zawiera cały kod niezbędny do tworzenia kopii zapasowych i przywracania plików zapisanych w pamięci wewnętrznej aplikacji.

Jednak odczytywanie i zapisywanie plików w pamięci wewnętrznej nie jest bezpieczne wątkowo. Aby mieć pewność, że agent kopii zapasowej nie odczytuje ani nie zapisuje plików w tym samym czasie co Ty, musisz używać zsynchronizowanych instrukcji za każdym razem, gdy wykonujesz operację odczytu lub zapisu. Na przykład w każdej aktywności, w której odczytujesz i zapisujesz plik, potrzebujesz obiektu, który będzie używany jako blokada wewnętrzna dla zsynchronizowanych instrukcji:

Kotlin

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

Java

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

Następnie utwórz zsynchronizowaną instrukcję z tą blokadą za każdym razem, gdy odczytujesz lub zapisujesz pliki. Oto przykład zsynchronizowanej instrukcji zapisywania w pliku najnowszego wyniku w grze:

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

Instrukcje odczytu należy synchronizować z tą samą blokadą.

Następnie w klasie BackupAgentHelper musisz zastąpić metody onBackup() i onRestore(), aby zsynchronizować operacje tworzenia i przywracania kopii zapasowej z tą samą blokadą wewnętrzną. Na przykład poprzedni przykład MyFileBackupAgent wymaga tych metod:

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

Rozszerzanie klasy BackupAgent

Większość aplikacji nie musi bezpośrednio rozszerzać klasy BackupAgent, ale powinna rozszerzać klasę BackupAgentHelper, aby korzystać z wbudowanych klas pomocniczych, które automatycznie tworzą kopie zapasowe plików i je przywracają. Możesz jednak rozszerzyć BackupAgent bezpośrednio, aby:

  • Używaj wersji formatu danych. Jeśli na przykład przewidujesz, że konieczna będzie zmiana formatu, w którym zapisujesz dane aplikacji, możesz utworzyć agenta kopii zapasowej, który podczas operacji przywracania sprawdzi wersję aplikacji i wykona niezbędne czynności, aby zapewnić zgodność, jeśli wersja na urządzeniu różni się od wersji danych kopii zapasowej. Więcej informacji znajdziesz w sekcji Sprawdzanie wersji danych do przywrócenia.
  • Określ, które dane mają być uwzględniane w kopii zapasowej. Zamiast tworzyć kopię zapasową całego pliku, możesz określić, które części danych mają być uwzględnione w kopii zapasowej i jak każda z nich ma być przywracana na urządzeniu. Może to też pomóc w zarządzaniu różnymi wersjami, ponieważ dane są odczytywane i zapisywane jako unikalne encje, a nie kompletne pliki.
  • Tworzenie kopii zapasowej danych w bazie danych Jeśli masz bazę danych SQLite, którą chcesz przywrócić, gdy użytkownik ponownie zainstaluje aplikację, musisz utworzyć niestandardowy obiekt BackupAgent, który odczytuje odpowiednie dane podczas operacji tworzenia kopii zapasowej, a następnie utworzyć tabelę i wstawić dane podczas operacji przywracania.

Jeśli nie musisz wykonywać żadnego z powyższych zadań i chcesz utworzyć kopię zapasową całych plików z SharedPreferences lub pamięci wewnętrznej, zapoznaj się z artykułem Rozszerzanie BackupAgentHelper.

Wymagane metody

Podczas tworzenia obiektu BackupAgent musisz zaimplementować te metody wywołania zwrotnego:

onBackup()
Menedżer kopii zapasowych wywołuje tę metodę, gdy poprosisz o utworzenie kopii zapasowej. W tej metodzie odczytujesz dane aplikacji z urządzenia i przekazujesz dane, których kopię zapasową chcesz utworzyć, do Menedżera kopii zapasowych, jak opisano w sekcji Tworzenie kopii zapasowej.
onRestore()

Menedżer kopii zapasowych wywołuje tę metodę podczas operacji przywracania. Ta metoda dostarcza dane kopii zapasowej, których aplikacja może użyć do przywrócenia poprzedniego stanu, zgodnie z opisem w sekcji Przywracanie.

System wywołuje tę metodę, aby przywrócić dane z kopii zapasowej, gdy użytkownik ponownie zainstaluje aplikację, ale aplikacja może też poprosić o przywrócenie.

Tworzenie kopii zapasowej

Żądanie utworzenia kopii zapasowej nie powoduje natychmiastowego wywołania metody onBackup(). Zamiast tego Menedżer kopii zapasowych czeka na odpowiedni moment, a następnie tworzy kopię zapasową wszystkich aplikacji, które wysłały żądanie utworzenia kopii zapasowej od czasu ostatniego utworzenia kopii zapasowej. W tym momencie musisz przekazać dane aplikacji do Menedżera kopii zapasowych, aby można je było zapisać w miejscu w chmurze.

Tylko Menedżer kopii zapasowych może wywołać metodę onBackup() agenta kopii zapasowej. Za każdym razem, gdy dane aplikacji się zmienią i chcesz utworzyć kopię zapasową, musisz poprosić o operację tworzenia kopii zapasowej, wywołując metodę dataChanged(). Więcej informacji znajdziesz w artykule Prośba o kopię zapasową.

Wskazówka: podczas tworzenia aplikacji możesz zainicjować natychmiastową operację tworzenia kopii zapasowej w Menedżerze kopii zapasowych za pomocą narzędzia bmgr.

Gdy menedżer kopii zapasowych wywołuje metodę onBackup(), przekazuje 3 parametry:

oldState
Otwarty, tylko do odczytu ParcelFileDescriptor wskazujący ostatni stan kopii zapasowej dostarczony przez aplikację. Nie są to dane kopii zapasowej z pamięci masowej w chmurze, ale lokalna reprezentacja danych, których kopia zapasowa została utworzona podczas ostatniego wywołania onBackup(), zgodnie z definicją newState lub z onRestore(). onRestore() jest opisany w następnej sekcji. onBackup() nie pozwala na odczyt istniejących danych kopii zapasowej w pamięci masowej w chmurze, dlatego możesz użyć tej lokalnej reprezentacji, aby określić, czy dane zmieniły się od czasu ostatniej kopii zapasowej.
data
Obiekt BackupDataOutput, którego używasz do dostarczania danych kopii zapasowej do Menedżera kopii zapasowych.
newState
Otwarty obiekt ParcelFileDescriptor do odczytu i zapisu wskazujący plik, w którym musisz zapisać reprezentację danych dostarczonych do data. Reprezentacją może być sygnatura czasowa ostatniej modyfikacji pliku. Ten obiekt jest zwracany jako oldState przy następnym wywołaniu metody onBackup() przez Menedżera kopii zapasowych. Jeśli nie zapiszesz danych kopii zapasowej w newState, oldState będzie wskazywać pusty plik, gdy Menedżer kopii zapasowych wywoła onBackup().

Za pomocą tych parametrów zaimplementuj metodę onBackup(), aby:

  1. Sprawdź, czy dane zmieniły się od czasu ostatniej kopii zapasowej, porównując oldState z bieżącymi danymi. Sposób odczytywania danych w oldState zależy od tego, jak zostały one pierwotnie zapisane w newState (patrz krok 3). Najprostszym sposobem na zarejestrowanie stanu pliku jest użycie sygnatury czasowej ostatniej modyfikacji. Oto przykład, jak możesz odczytać i porównać sygnaturę czasową z 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
    }
    

    Jeśli nic się nie zmieniło i nie musisz tworzyć kopii zapasowej, przejdź do kroku 3.

  2. Jeśli dane uległy zmianie w porównaniu z oldState, zapisz bieżące dane w data, aby utworzyć ich kopię zapasową w miejscu w chmurze.

    Każdy fragment danych musisz zapisać jako encję w BackupDataOutput. Encja to spłaszczony rekord danych binarnych, który jest identyfikowany przez unikalny ciąg klucza. Zbiór danych, którego kopię zapasową tworzysz, jest więc koncepcyjnie zbiorem par klucz-wartość.

    Aby dodać encję do zbioru danych kopii zapasowej, musisz:

    1. Wywołaj funkcję writeEntityHeader(), przekazując unikalny klucz ciągu znaków dla danych, które chcesz zapisać, oraz rozmiar danych.

    2. Wywołaj funkcję writeEntityData(), przekazując bufor bajtów zawierający dane i liczbę bajtów do zapisania z bufora, która powinna odpowiadać rozmiarowi przekazanemu do funkcji writeEntityHeader().

    Na przykład ten kod spłaszcza niektóre dane do strumienia bajtów i zapisuje je w jednej encji:

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

    Wykonaj tę czynność w przypadku wszystkich danych, których kopię zapasową chcesz utworzyć. Sposób podziału danych na elementy zależy od Ciebie. Możesz nawet użyć tylko jednej encji.

  3. Niezależnie od tego, czy utworzysz kopię zapasową (w kroku 2), zapisz reprezentację bieżących danych w newState ParcelFileDescriptor. Menedżer kopii zapasowych zachowuje ten obiekt lokalnie jako reprezentację danych, których kopia zapasowa jest obecnie tworzona. Przekazuje Ci tę wartość jako oldState przy następnym wywołaniu metody onBackup(), aby umożliwić Ci określenie, czy konieczne jest utworzenie kolejnej kopii zapasowej, jak opisano w kroku 1. Jeśli nie zapiszesz bieżącego stanu danych w tym pliku, podczas następnego wywołania zwrotnego parametr oldState będzie pusty.

    W tym przykładzie reprezentacja bieżących danych jest zapisywana w newState przy użyciu sygnatury czasowej ostatniej modyfikacji pliku:

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

Przywracanie

Gdy nadejdzie czas na przywrócenie danych aplikacji, menedżer kopii zapasowych wywoła metodę onRestore() agenta kopii zapasowej. Gdy wywołuje tę metodę, Menedżer kopii zapasowych dostarcza dane z kopii zapasowej, aby można było je przywrócić na urządzeniu.

Tylko Menedżer kopii zapasowych może wywołać metodę onRestore(), co dzieje się automatycznie, gdy system instaluje aplikację i znajduje istniejące dane kopii zapasowej.

Gdy menedżer kopii zapasowych wywołuje metodę onRestore(), przekazuje 3 parametry:

data
Obiekt BackupDataInput, który umożliwia odczyt danych z kopii zapasowej.
appVersionCode
Liczba całkowita reprezentująca wartość atrybutu manifestu android:versionCode aplikacji w momencie tworzenia kopii zapasowej tych danych. Możesz użyć tej informacji, aby sprawdzić bieżącą wersję aplikacji i ustalić, czy format danych jest zgodny. Więcej informacji o używaniu tej metody do obsługi różnych wersji danych przywracania znajdziesz w sekcji Sprawdzanie wersji danych przywracania.
newState
Otwarty obiekt ParcelFileDescriptor do odczytu i zapisu wskazujący plik, w którym musisz zapisać ostateczny stan kopii zapasowej podany w parametrze data. Ten obiekt jest zwracany jako oldState przy następnym wywołaniu onBackup(). Pamiętaj, że musisz też zapisać ten sam obiekt newState w wywołaniu zwrotnym onBackup(). Zrobienie tego tutaj gwarantuje, że obiekt oldState przekazany do onBackup() będzie prawidłowy nawet przy pierwszym wywołaniu onBackup() po przywróceniu urządzenia.

W implementacji onRestore() musisz wywołać readNextHeader() na data, aby przejść przez wszystkie encje w zbiorze danych. W przypadku każdej znalezionej encji wykonaj te czynności:

  1. Pobierz klucz encji za pomocą metody getKey().
  2. Porównaj klucz encji z listą znanych wartości kluczy, które powinny być zadeklarowane jako statyczne ciągi znaków final w klasie BackupAgent. Gdy klucz pasuje do jednego ze znanych ciągów kluczy, wpisz instrukcję, aby wyodrębnić dane encji i zapisać je na urządzeniu:

    1. Pobierz rozmiar danych encji za pomocą getDataSize() i utwórz tablicę bajtów o tym rozmiarze.
    2. Wywołaj readEntityData() i przekaż tablicę bajtów, do której trafią dane, oraz określ przesunięcie początkowe i rozmiar do odczytu.
    3. Tablica bajtów jest teraz pełna. Odczytaj dane i zapisz je na urządzeniu w dowolny sposób.
  3. Po odczytaniu i zapisaniu danych z powrotem na urządzeniu zapisz stan danych w parametrze newState tak samo jak podczas onBackup().

Oto przykład, jak możesz przywrócić dane, których kopię zapasową utworzono w przykładzie z poprzedniej sekcji:

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

W tym przykładzie parametr appVersionCode przekazany do funkcji onRestore() nie jest używany. Możesz jednak użyć tej metody, jeśli chcesz wykonać kopię zapasową, gdy wersja aplikacji użytkownika została cofnięta (np. użytkownik przeszedł z wersji 1.5 aplikacji na wersję 1.0). Więcej informacji znajdziesz w następnej sekcji.

Sprawdzanie wersji danych do przywrócenia

Gdy Menedżer kopii zapasowych zapisuje dane w miejscu w chmurze, automatycznie uwzględnia wersję aplikacji określoną przez atrybut android:versionCode pliku manifestu. Zanim Menedżer kopii zapasowych wywoła agenta kopii zapasowej w celu przywrócenia danych, sprawdza android:versionCode aplikacji instalowanej i porównuje go z wartością zapisaną w zbiorze danych do przywrócenia. Jeśli wersja zapisana w zbiorze danych przywracania jest nowsza niż wersja aplikacji na urządzeniu, oznacza to, że użytkownik zainstalował starszą wersję aplikacji. W takim przypadku Menedżer kopii zapasowych przerwie operację przywracania aplikacji i nie wywoła metody onRestore(), ponieważ zbiór przywracania jest uznawany za nieprzydatny w starszej wersji.

Możesz zastąpić to działanie, używając atrybutu android:restoreAnyVersion:

  • false (domyślna): menedżer kopii zapasowych przerywa operację przywracania, jeśli wersja zapisana w zbiorze danych przywracania jest nowsza niż wersja aplikacji na urządzeniu.
  • true: menedżer kopii zapasowych ignoruje android:versionCode i we wszystkich przypadkach wywołuje metodę onRestore(), dzięki czemu możesz ręcznie sprawdzić różnice między wersjami i zapewnić zgodność danych.

Aby ułatwić obsługę różnych wersji podczas operacji przywracania, metoda onRestore() przekazuje kod wersji dołączony do zbioru danych przywracania jako parametr appVersionCode. Następnie możesz sprawdzić kod wersji bieżącej aplikacji za pomocą pola PackageInfo.versionCode. Przykład:

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

Następnie porównaj wartość version uzyskaną z PackageInfo z wartością appVersionCode przekazaną do onRestore().

Prośba o utworzenie kopii zapasowej

W każdej chwili możesz poprosić o wykonanie operacji tworzenia kopii zapasowej, wywołując funkcję dataChanged(). Ta metoda powiadamia Menedżera kopii zapasowych, że chcesz utworzyć kopię zapasową danych za pomocą agenta kopii zapasowych. Następnie Menedżer kopii zapasowych wywołuje metodę onBackup() agenta kopii zapasowej w przyszłości. Zwykle należy poprosić o utworzenie kopii zapasowej za każdym razem, gdy dane ulegną zmianie (np. gdy użytkownik zmieni ustawienia aplikacji, które chcesz uwzględnić w kopii zapasowej). Jeśli wywołasz metodę dataChanged() kilka razy, zanim Menedżer kopii zapasowych poprosi agenta o utworzenie kopii zapasowej, agent i tak otrzyma tylko jedno wywołanie metody onBackup().

Prośba o przywrócenie

W normalnym cyklu życia aplikacji nie powinno być potrzeby żądania operacji przywracania. System automatycznie sprawdza dane kopii zapasowej i przywraca je po zainstalowaniu aplikacji.

Przejście na automatyczną kopię zapasową

Możesz przejść na pełne kopie zapasowe danych, ustawiając android:fullBackupOnly na true w elemencie <application> w pliku manifestu. Jeśli aplikacja działa na urządzeniu z Androidem 5.1 (poziom interfejsu API 22) lub starszym, ignoruje tę wartość w pliku manifestu i nadal wykonuje kopie zapasowe klucz-wartość. Gdy aplikacja działa na urządzeniu z Androidem 6.0 (poziom interfejsu API 23) lub nowszym, wykonuje automatyczną kopię zapasową zamiast kopii zapasowej klucz-wartość.

Prywatność użytkowników

W Google doskonale zdajemy sobie sprawę z zaufania, jakim obdarzają nas użytkownicy, i z tego, że mamy obowiązek chronić ich prywatność. Google bezpiecznie przesyła dane kopii zapasowej na serwery Google i z nich, aby zapewnić funkcje tworzenia kopii zapasowej i przywracania danych. Google traktuje te dane jako dane osobowe zgodnie ze swoją Polityką prywatności.

Użytkownicy mogą też wyłączyć funkcję tworzenia kopii zapasowej danych w ustawieniach kopii zapasowej systemu Android. Gdy użytkownik wyłączy tworzenie kopii zapasowej, Android Backup Service usunie wszystkie zapisane dane kopii zapasowej. Użytkownik może ponownie włączyć tworzenie kopii zapasowej na urządzeniu, ale Android Backup Service nie przywróci wcześniej usuniętych danych.