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

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

Maksymalny rozmiar danych to 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 tworzyć i przywracać, znajdziesz w omówieniu kopii zapasowych danych.

Wdrażanie kopii zapasowych par klucz-wartość

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

Aby wdrożyć agenta zapasowego, musisz:

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

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

    • Przedłużam BackupAgent

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

    • Przedłużam BackupAgentHelper

      Klasa BackupAgentHelper zapewnia wygodne opakowanie klasy BackupAgent, która zmniejsza ilość kodu do napisania. W BackupAgentHelper musisz używać co najmniej 1 obiektu pomocniczego, który automatycznie tworzy i przywraca kopie zapasowe określonych typów danych. Dzięki temu nie musisz implementować onBackup() i onRestore(). Jeśli nie potrzebujesz pełnej kontroli nad kopiami zapasowymi aplikacji, do ich obsługi zalecamy korzystanie z BackupAgentHelper.

      Android udostępnia obecnie narzędzia pomocnicze do tworzenia kopii zapasowych, które tworzą i przywracają pełne pliki z SharedPreferences i pamięci wewnętrznej.

Zadeklaruj agenta zapasowego w pliku manifestu

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

Na 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 obsługiwać starsze urządzenia, zalecamy dodanie klucza interfejsu API <meta-data> do pliku manifestu Androida. Android Backup Service nie wymaga już klucza usługi, ale niektóre starsze urządzenia mogą nadal go sprawdzać 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ą wskazującą, czy chcesz przywrócić dane aplikacji niezależnie od jej obecnej wersji w porównaniu z wersją, która wygenerowała kopię zapasową danych. Wartością domyślną jest false. Więcej informacji znajdziesz w sekcji Sprawdzanie przywracania danych.

Rozszerzenie usługi BackupAgentHelper

Jeśli chcesz tworzyć kopie zapasowe kompletnych plików z SharedPreferences lub pamięci wewnętrznej, utwórz agenta kopii zapasowej za pomocą usługi BackupAgentHelper. Do utworzenia agenta zapasowego za pomocą BackupAgentHelper wymaga znacznie mniej kodu niż rozszerzenie BackupAgent, ponieważ nie musisz implementować onBackup() ani onRestore().

Twoja implementacja BackupAgentHelper musi korzystać z co najmniej 1 zapasowego elementu pomocniczego. Asystent kopii zapasowych to wyspecjalizowany komponent, który BackupAgentHelper przywołuje w celu wykonywania operacji tworzenia i przywracania kopii zapasowej określonego typu danych. Platforma Androida udostępnia obecnie 2 różne pomoce:

W elemencie BackupAgentHelper możesz uwzględnić wielu pomocników, ale dla każdego typu danych potrzeba tylko 1 takiego elementu. Oznacza to, że jeśli masz wiele plików SharedPreferences, potrzebujesz tylko jednego z nich SharedPreferencesBackupHelper.

W przypadku każdego pomocnika, który chcesz dodać do usługi BackupAgentHelper, musisz wykonać te czynności podczas korzystania z metody onCreate():

  1. Utwórz instancję wybranej klasy pomocniczej. W konstruktorze klas musisz określić pliki, których kopie zapasowe chcesz utworzyć.
  2. Zadzwoń pod numer addHelper(), aby dodać pomocniczą do: BackupAgentHelper.

Sekcje poniżej opisują, jak utworzyć agenta kopii zapasowej za pomocą każdej z dostępnych opcji pomocniczych.

Utwórz kopię zapasową SharedPreferences

Gdy tworzysz instancję SharedPreferencesBackupHelper, musisz dodać nazwę co najmniej jednego pliku SharedPreferences.

Aby na przykład utworzyć kopię zapasową pliku SharedPreferences o nazwie user_preferences, agent tworzenia 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);
    }
}

Pole 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 pomocnicze kopie zapasowe, aby utworzyć kopię zapasową i przywrócić określone pliki.

Tworzenie kopii zapasowych innych plików

Gdy tworzysz instancję FileBackupHelper, musisz podać nazwę co najmniej jednego pliku zapisanego w pamięci wewnętrznej aplikacji wskazaną w polu getFilesDir(), czyli tej samej lokalizacji, w której openFileOutput() zapisuje pliki.

Aby na przykład utworzyć kopię zapasową 2 plików o nazwie 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);
    }
}

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

Jednak odczyt i zapis plików w pamięci wewnętrznej nie jest bezpieczny dla wątków. Aby agent kopii zapasowej nie odczytywał ani nie zapisywał plików w tym samym czasie co działania, używaj zsynchronizowanych instrukcji podczas każdego odczytu lub zapisu. Na przykład w każdym działaniu, w którym czytasz i zapisujesz plik, potrzebujesz obiektu, który będzie służyć jako wewnętrzna blokada 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 twórz instrukcję zsynchronizowaną z tą blokadą za każdym razem, gdy będziesz odczytywać lub zapisywać pliki. Na przykład takie zsynchronizowane polecenie zapisu 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");
}

Wyciągi czytania powinny być synchronizowane z tą samą kłódką.

Następnie w BackupAgentHelper musisz zastąpić zasady onBackup() i onRestore(), aby synchronizować operacje tworzenia i przywracania kopii zapasowej z tą samą blokadą wewnętrzną. Na przykład podany wyżej 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);
    }
}

Rozszerz agenta kopii zapasowej

Większość aplikacji nie powinna wymagać bezpośredniego rozszerzania klasy BackupAgent. Zamiast tego rozszerz BackupAgentHelper, aby korzystać z wbudowanych klas pomocniczych, które automatycznie tworzą kopie zapasowe plików i przywracają je. Możesz jednak rozszerzyć adres BackupAgent bezpośrednio o te funkcje:

  • Wersja formatu danych: Jeśli na przykład przewidujesz konieczność zmiany formatu zapisu danych aplikacji, możesz utworzyć agenta zapasowego, który będzie sprawdzał wersję aplikacji podczas operacji przywracania i wykonał niezbędne działania w zakresie zgodności, jeśli wersja na urządzeniu różni się od wersji na kopii zapasowej. Więcej informacji znajdziesz w sekcji Sprawdzanie wersji danych do przywracania.
  • Określanie części danych, których kopię zapasową chcesz utworzyć Zamiast tworzyć kopię zapasową całego pliku, możesz określić, które z danych plików mają być zapisywane w kopii zapasowej oraz w jaki sposób mają być one przywracane na urządzeniu. Może to też ułatwić zarządzanie różnymi wersjami, ponieważ dane są odczytywane i zapisywane jako niepowtarzalne elementy, a nie pełne pliki.
  • Tworzenie kopii zapasowej danych w bazie danych. Jeśli masz bazę danych SQLite, którą chcesz przywrócić, gdy użytkownik ponownie zainstaluje Twoją aplikację, musisz utworzyć niestandardową instancję BackupAgent, która będzie odczytywać odpowiednie dane podczas operacji tworzenia kopii zapasowej, a potem utworzyć tabelę i wstawić do niej dane podczas operacji przywracania.

Jeśli nie musisz wykonywać żadnej z powyższych czynności, a chcesz tworzyć kopie zapasowe wszystkich plików z usługi SharedPreferences lub pamięci wewnętrznej, zapoznaj się z artykułem Przedłużanie dostępu BackupAgentHelper.

Wymagane metody

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

onBackup()
Menedżer kopii zapasowych wywołuje tę metodę, gdy prosisz o utworzenie kopii zapasowej. Ta metoda polega na odczytywaniu danych aplikacji z urządzenia i przekazywaniu tych, których kopie zapasowe chcesz utworzyć w Menedżerze kopii zapasowych, zgodnie z opisem w sekcji Wykonywanie 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 jej poprzedniego stanu zgodnie z opisem w sekcji Przywracanie.

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

Utwórz kopię zapasową

Żądanie utworzenia kopii zapasowej nie powoduje natychmiastowego wywołania metody onBackup(). Zamiast tego czeka na odpowiedni czas, a potem tworzy kopię zapasową wszystkich aplikacji, które zażądały utworzenia kopii zapasowej od czasu utworzenia ostatniej kopii zapasowej. Na tym etapie musisz przekazać dane aplikacji do Menedżera kopii zapasowych, aby można je było zapisać w chmurze.

Tylko Menedżer kopii zapasowych może wywoływać metodę onBackup() agenta zapasowego. Za każdym razem, gdy dane aplikacji ulegną zmianie i chcesz utworzyć kopię zapasową, musisz wysłać żądanie wykonania operacji tworzenia kopii zapasowej, wywołując metodę dataChanged(). Więcej informacji znajdziesz w artykule Przesyłanie prośby 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
Otwarta, dostępna tylko do odczytu wartość ParcelFileDescriptor wskazująca stan ostatniej kopii zapasowej przez Twoją aplikację. Nie jest to dane kopii zapasowej z pamięci w chmurze, ale lokalna reprezentacja danych, które zostały zapisane przy ostatnim wywołaniu usługi onBackup(), zgodnie z definicją w newState lub onRestore(). onRestore() został omówiony w następnej sekcji. Ponieważ właściwość onBackup() nie pozwala na odczytywanie danych z kopii zapasowej w chmurze, możesz użyć tej lokalnej reprezentacji, aby określić, czy dane zmieniły się od czasu utworzenia ostatniej kopii zapasowej.
data
Obiekt BackupDataOutput, który służy do przesyłania danych kopii zapasowej do Menedżera kopii zapasowych.
newState
Otwarty plik ParcelFileDescriptor do odczytu i zapisu, który wskazuje plik, w którym musisz umieścić reprezentację danych przesłanych do usługi data. Może to być sygnatura czasowa ostatniej modyfikacji pliku. Ten obiekt jest zwracany jako oldState, gdy następnym razem Menedżer kopii zapasowych wywoła Twoją metodę onBackup(). Jeśli nie zapiszesz danych kopii zapasowej w usłudze newState, następnym razem, gdy Menedżer kopii zapasowych wywoła onBackup(), oldState wskaże pusty plik.

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

  1. Sprawdź, czy dane zmieniły się od czasu utworzenia ostatniej kopii zapasowej, porównując oldState z danymi bieżącymi. Sposób odczytu danych w usłudze oldState zależy od tego, jak zostały pierwotnie zapisane w usłudze newState (patrz krok 3). Najłatwiejszym sposobem rejestrowania stanu pliku jest podanie sygnatury czasowej ostatniej modyfikacji. Na przykład w ten sposób możesz odczytać i porównać sygnaturę czasową z tabeli 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 Twoje dane uległy zmianie w porównaniu z oldState, zapisz bieżące dane w usłudze data, aby utworzyć ich kopię zapasową w chmurze.

    Każdy fragment danych musisz zapisać jako encję w komponencie BackupDataOutput. Element to spłaszczony rekord danych binarnych identyfikowany przez unikalny ciąg klucza. Oznacza to, że kopia zapasowa tworzonego przez Ciebie zbioru danych jest koncepcyjnie zbiorem par klucz-wartość.

    Aby dodać encję do zbioru danych kopii zapasowej:

    1. Wywołaj metodę writeEntityHeader(), która przekazuje unikalny klucz ciągu znaków odpowiadający danym, które zamierzasz zapisać, oraz rozmiarowi danych.

    2. Wywołaj metodę writeEntityData(), aby przekazać bufor zawierający Twoje dane oraz liczbę bajtów do zapisania z bufora. Powinna ona odpowiadać rozmiarowi przekazanemu do writeEntityHeader().

    Na przykład ten kod dzieli część danych do strumienia bajtów i zapisuje je w jedną encję:

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

    Wykonaj tę czynność dla każdego fragmentu danych, którego kopię zapasową chcesz utworzyć. Sposób podziału danych na encje zależy tylko od Ciebie. Możesz nawet użyć tylko jednego elementu.

  3. Niezależnie od tego, czy utworzysz kopię zapasową (w kroku 2), zapisz bieżące dane w interfejsie newState ParcelFileDescriptor. Menedżer kopii zapasowych przechowuje ten obiekt lokalnie jako reprezentację danych, które mają obecnie kopię zapasową. Przekazuje go z powrotem do Ciebie jako oldState przy następnym wywołaniu funkcji onBackup(), dzięki czemu możesz określić, czy potrzebna jest nowa kopia zapasowa, jak opisano w kroku 1. Jeśli nie zapiszesz w tym pliku bieżącego stanu danych, pole oldState będzie puste podczas następnego wywołania zwrotnego.

    Ten przykład umożliwia zapisanie reprezentacji bieżących danych w elemencie newState za pomocą 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 przywrócenia danych aplikacji, Menedżer kopii zapasowych wywoła metodę onRestore() agenta zapasowego. Wywołując tę metodę, Menedżer kopii zapasowej dostarcza dane z kopii zapasowej, którą można przywrócić na urządzenie.

Tylko Menedżer kopii zapasowych może wywołać metodę onRestore(), co następuje automatycznie, gdy system zainstaluje aplikację i znajduje dane kopii zapasowej.

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

data
Obiekt BackupDataInput, który umożliwia odczyt danych kopii zapasowej.
appVersionCode
Liczba całkowita określająca wartość atrybutu manifestu android:versionCode w takiej postaci, w jakiej miała miejsce kopia zapasowa tych danych. Dzięki temu możesz sprawdzić obecną wersję aplikacji i ustalić, czy format danych jest zgodny. Więcej informacji o korzystaniu z tej funkcji do obsługi różnych wersji przywracania danych znajdziesz w sekcji Sprawdzanie przywracania danych.
newState
Otwarta, do odczytu i zapisu ParcelFileDescriptor wskazująca plik, w którym musisz wpisać ostateczny stan kopii zapasowej dostarczony z data. Ten obiekt jest zwracany jako oldState przy następnym wywołaniu obiektu onBackup(). Pamiętaj, że w wywołaniu zwrotnym onBackup() musisz też zapisać ten sam obiekt newState. Dzięki temu będziesz mieć pewność, że obiekt oldState przekazany do onBackup() będzie prawidłowy nawet przy pierwszym wywołaniu onBackup() po przywróceniu urządzenia.

W swojej implementacji onRestore() musisz wywołać metodę readNextHeader() w data, aby iterować wszystkie encje w zbiorze danych. W przypadku każdego znalezionego elementu wykonaj te czynności:

  1. Pobierz klucz encji za pomocą getKey().
  2. Porównaj klucz encji z listą znanych wartości klucza, które należy zadeklarować jako statyczne ciągi końcowe 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 metodę readEntityData() i przekaż do niej tablicę bajtów, na którą zostaną przeniesione dane, i określ przesunięcie rozpoczęcia oraz rozmiar do odczytania.
    3. Tablica bajtów jest teraz pełna. Możesz odczytywać dane i zapisywać je w dowolnym momencie na urządzeniu.
  3. Po odczytaniu i zapisaniu danych na urządzeniu zapisz w parametrze newState stan swoich danych tak samo jak w przypadku użycia funkcji onBackup().

Możesz np. przywrócić dane z kopii zapasowej utworzonej zgodnie z przykładem 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 onRestore() nie jest używany. Może Ci się jednak przydać, jeśli masz włączone tworzenie kopii zapasowej, gdy wersja aplikacji cofnięta została przez użytkownika (np. gdy użytkownik przeszedł z wersji 1.5 aplikacji do wersji 1.0). Więcej informacji znajdziesz w następnej sekcji.

Sprawdzanie wersji przywracania danych

Gdy Menedżer kopii zapasowych zapisuje dane w chmurze, automatycznie dołącza wersję aplikacji zgodnie z ustawieniem atrybutu android:versionCode w pliku manifestu. Zanim Menedżer kopii zapasowych wywoła agenta kopii zapasowej w celu przywrócenia danych, sprawdza android:versionCode zainstalowanej aplikacji i porównuje ją z wartością zarejestrowaną w zbiorze danych przywracania. Jeśli wersja zarejestrowana w zbiorze danych przywracania jest nowsza niż wersja aplikacji na urządzeniu, oznacza to, że użytkownik zmienił wersję aplikacji na starszą. W takim przypadku Menedżer kopii zapasowych przerwie operację przywracania aplikacji i nie wywoła metody onRestore(), ponieważ zestaw przywracania jest uznawany za nieznaczący dla starszej wersji.

Możesz zastąpić to działanie za pomocą atrybutu android:restoreAnyVersion. Ustaw ten atrybut na true, aby wskazać, że chcesz przywrócić aplikację niezależnie od wersji zestawu przywracania. Wartością domyślną jest false. Jeśli ustawisz tę wartość na true, Menedżer kopii zapasowych zignoruje android:versionCode i we wszystkich przypadkach będzie wywoływać metodę onRestore(). W takim przypadku możesz ręcznie sprawdzić różnicę w wersji metody onRestore() i podjąć odpowiednie kroki w celu zapewnienia zgodności danych, jeśli wersje się nie zgadzają.

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 wysłać zapytanie o kod wersji bieżącej aplikacji, korzystając z pola PackageInfo.versionCode. Na 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 version z zewnątrz PackageInfo z danymi appVersionCode przekazanymi do onRestore().

Poproś o kopię zapasową

W każdej chwili możesz poprosić o utworzenie kopii zapasowej, wywołując dataChanged(). Ta metoda powiadamia Menedżera kopii zapasowych, że chcesz utworzyć kopię zapasową danych przy użyciu agenta kopii zapasowej. Menedżer kopii zapasowych wywoła wtedy metodę onBackup() agenta kopii zapasowej w przyszłości. Zwykle prośbę o utworzenie kopii zapasowej należy wysłać po każdej zmianie danych (np. gdy użytkownik zmieni ustawienie aplikacji, której kopię zapasową chcesz utworzyć). Jeśli wywołasz dataChanged() kilka razy, zanim Menedżer kopii zapasowych zażąda utworzenia kopii zapasowej od agenta, agent nadal otrzyma tylko 1 wywołanie do onBackup().

Poproś o przywrócenie

W trakcie normalnego życia aplikacji nie musisz prosić o operację przywracania. System automatycznie sprawdza dostępność danych kopii zapasowej i przywraca ją po zainstalowaniu aplikacji.

Migracja do Automatycznej kopii zapasowej

Aby przejść na tworzenie pełnych kopii zapasowych danych, ustaw parametr android:fullBackupOnly na true w elemencie <application> w pliku manifestu. Gdy aplikacja działa na urządzeniu z Androidem 5.1 (poziom interfejsu API 22) lub niższym, ignoruje tę wartość w pliku manifestu i nadal wykonuje kopie zapasowe par klucz-wartość. Jeśli aplikacja działa na urządzeniu z Androidem 6.0 (poziom interfejsu API 23) lub nowszym, zamiast tworzyć kopie zapasowe par klucz-wartość, używa ona Automatycznej kopii zapasowej.

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ć działanie funkcji tworzenia i przywracania kopii zapasowych. Google traktuje te dane jako dane osobowe zgodnie ze swoją Polityką prywatności.

Oprócz tego użytkownicy mogą wyłączyć tę funkcję w ustawieniach kopii zapasowej systemu Android. Gdy użytkownik wyłączy kopię zapasową, 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 żadnych wcześniej usuniętych danych.