Резервное копирование пар ключ-значение с помощью службы резервного копирования Android.

Служба резервного копирования Android обеспечивает резервное копирование и восстановление данных «ключ-значение» в облачном хранилище вашего приложения Android. Во время операции резервного копирования «ключ-значение» данные резервной копии приложения передаются в резервный транспорт устройства. Если устройство использует транспорт резервного копирования Google по умолчанию, данные передаются в службу резервного копирования Android для архивирования.

Объем данных ограничен 5 МБ на каждого пользователя вашего приложения. За хранение резервных данных плата не взимается.

Обзор возможностей резервного копирования Android и рекомендации о том, какие данные следует выполнять резервное копирование и восстановление, см. в разделе Обзор резервного копирования данных .

Реализация резервного копирования «ключ-значение»

Чтобы создать резервную копию данных вашего приложения, вам необходимо внедрить агент резервного копирования. Ваш агент резервного копирования вызывается диспетчером резервного копирования как во время резервного копирования, так и во время восстановления.

Для реализации агента резервного копирования необходимо:

  1. Объявите свой агент резервного копирования в файле манифеста с атрибутом android:backupAgent .

  2. Определите агент резервного копирования, выполнив одно из следующих действий:

    • Расширение BackupAgent

      Класс BackupAgent предоставляет центральный интерфейс, который ваше приложение использует для связи с диспетчером резервного копирования. Если вы расширяете этот класс напрямую, вам необходимо переопределить onBackup() и onRestore() для выполнения операций резервного копирования и восстановления ваших данных.

    • Расширение BackupAgentHelper

      Класс BackupAgentHelper представляет собой удобную оболочку для класса BackupAgent , сводя к минимуму объем кода, который необходимо написать. В вашем BackupAgentHelper вы должны использовать один или несколько вспомогательных объектов, которые автоматически создают резервные копии и восстанавливают определенные типы данных, поэтому вам не нужно реализовывать onBackup() и onRestore() . Если вам не нужен полный контроль над резервными копиями вашего приложения, мы рекомендуем использовать BackupAgentHelper для обработки резервных копий вашего приложения.

      В настоящее время Android предоставляет помощники по резервному копированию, которые позволяют создавать резервные копии и восстанавливать полные файлы из SharedPreferences и внутреннего хранилища .

Объявите агент резервного копирования в своем манифесте.

После того, как вы определились с именем класса для своего агента резервного копирования, объявите его в своем манифесте, используя атрибут android:backupAgent в теге <application> .

Например:

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

Для поддержки старых устройств мы рекомендуем добавить ключ API <meta-data> в файл манифеста Android. Службе резервного копирования Android больше не требуется служебный ключ, но некоторые старые устройства все равно могут проверять наличие ключа при резервном копировании. Установите android:name значение com.google.android.backup.api_key и android:value unused .

Атрибут android:restoreAnyVersion принимает логическое значение, указывающее, хотите ли вы восстановить данные приложения независимо от текущей версии приложения по сравнению с версией, в которой были созданы данные резервной копии. Значение по умолчанию — false . Дополнительную информацию см. в разделе Проверка версии данных восстановления .

Расширение BackupAgentHelper

Вам следует создать свой агент резервного копирования с помощью BackupAgentHelper если вы хотите выполнять резервное копирование полных файлов из SharedPreferences или внутреннего хранилища. Для создания агента резервного копирования с помощью BackupAgentHelper требуется гораздо меньше кода, чем для расширения BackupAgent , поскольку вам не нужно реализовывать onBackup() и onRestore() .

Ваша реализация BackupAgentHelper должна использовать один или несколько помощников резервного копирования. Помощник резервного копирования — это специализированный компонент, который вызывает BackupAgentHelper для выполнения операций резервного копирования и восстановления для определенного типа данных. Платформа Android в настоящее время предоставляет два разных помощника:

  • SharedPreferencesBackupHelper для резервного копирования файлов SharedPreferences .
  • FileBackupHelper для резервного копирования файлов из внутреннего хранилища.

Вы можете включить несколько помощников в свой BackupAgentHelper , но для каждого типа данных необходим только один помощник. То есть, если у вас есть несколько файлов SharedPreferences , вам понадобится только один SharedPreferencesBackupHelper .

Для каждого помощника, который вы хотите добавить в свой BackupAgentHelper , вы должны сделать следующее во время метода onCreate() :

  1. Создайте экземпляр желаемого вспомогательного класса. В конструкторе класса вы должны указать файлы, резервную копию которых хотите создать.
  2. Вызовите addHelper() чтобы добавить помощника в BackupAgentHelper .

В следующих разделах описывается, как создать агент резервного копирования, используя каждый из доступных помощников.

Резервное копирование общих настроек

При создании экземпляра SharedPreferencesBackupHelper необходимо указать имя одного или нескольких файлов SharedPreferences .

Например, для резервного копирования файла SharedPreferences с именем user_preferences полный агент резервного копирования с использованием BackupAgentHelper выглядит следующим образом:

Котлин

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

Ява

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 включает в себя весь код, необходимый для резервного копирования и восстановления файла SharedPreferences .

Когда диспетчер резервного копирования вызывает onBackup() и onRestore() , BackupAgentHelper вызывает ваши помощники по резервному копированию для резервного копирования и восстановления указанных файлов.

Резервное копирование других файлов

Когда вы создаете экземпляр FileBackupHelper , вы должны включить имя одного или нескольких файлов, которые сохраняются во внутреннем хранилище вашего приложения, как указано в getFilesDir() , которое является тем же местом, где openFileOutput() записывает файлы.

Например, для резервного копирования двух файлов с именами scores и stats агент резервного копирования, использующий BackupAgentHelper выглядит следующим образом:

Котлин

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

Ява

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 включает в себя весь код, необходимый для резервного копирования и восстановления файлов, сохраненных во внутренней памяти вашего приложения.

Однако чтение и запись файлов во внутренней памяти не является потокобезопасным . Чтобы гарантировать, что ваш агент резервного копирования не читает и не записывает ваши файлы одновременно с вашими действиями, вы должны использовать синхронизированные операторы каждый раз, когда вы выполняете чтение или запись. Например, в любом действии, где вы читаете и записываете файл, вам нужен объект, который будет использоваться в качестве внутренней блокировки для синхронизированных операторов:

Котлин

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

Ява

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

Затем создайте синхронизированный оператор с этой блокировкой каждый раз, когда вы читаете или записываете файлы. Например, вот синхронизированный оператор для записи последнего результата в игре в файл:

Котлин

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

Ява

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

Вам следует синхронизировать операторы чтения с одной и той же блокировкой.

Затем в BackupAgentHelper вы должны переопределить onBackup() и onRestore() чтобы синхронизировать операции резервного копирования и восстановления с одной и той же внутренней блокировкой. Например, приведенному выше примеру MyFileBackupAgent необходимы следующие методы:

Котлин

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

Ява

@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 , вместо этого им следует расширять BackupAgentHelper чтобы воспользоваться преимуществами встроенных вспомогательных классов, которые автоматически создают резервные копии и восстанавливают ваши файлы. Однако вы можете напрямую расширить BackupAgent , чтобы выполнять следующие действия:

  • Версия вашего формата данных. Например, если вы ожидаете необходимости пересмотреть формат, в котором вы записываете данные своего приложения, вы можете создать агент резервного копирования для перекрестной проверки версии вашего приложения во время операции восстановления и выполнения любых необходимых работ по совместимости, если версия на устройстве отличается от данных резервной копии. Дополнительную информацию см. в разделе Проверка версии данных восстановления .
  • Укажите части данных для резервного копирования. Вместо резервного копирования всего файла вы можете указать части данных для резервного копирования и способ последующего восстановления каждой части на устройство. Это также может помочь вам управлять разными версиями, поскольку вы читаете и записываете данные как уникальные объекты, а не как целые файлы.
  • Резервное копирование данных в базе данных. Если у вас есть база данных SQLite, которую вы хотите восстановить, когда пользователь переустанавливает ваше приложение, вам необходимо создать собственный BackupAgent , который считывает соответствующие данные во время операции резервного копирования, а затем создает таблицу и вставляет данные во время операции восстановления.

Если вам не нужно выполнять какие-либо из вышеперечисленных задач и вы хотите выполнить резервное копирование полных файлов из SharedPreferences или внутреннего хранилища, см. Расширение BackupAgentHelper .

Требуемые методы

При создании BackupAgent необходимо реализовать следующие методы обратного вызова:

onBackup()
Менеджер резервного копирования вызывает этот метод после запроса резервной копии . В этом методе вы считываете данные своего приложения с устройства и передаете данные, резервную копию которых хотите создать, диспетчеру резервного копирования, как описано в разделе «Выполнение резервного копирования» .
onRestore()

Диспетчер резервного копирования вызывает этот метод во время операции восстановления. Этот метод предоставляет данные резервной копии, которые ваше приложение может использовать для восстановления прежнего состояния, как описано в разделе «Выполнение восстановления» .

Система вызывает этот метод для восстановления любых данных резервной копии, когда пользователь переустанавливает ваше приложение, но ваше приложение также может запросить восстановление .

Выполните резервное копирование

Запрос на резервное копирование не приводит к немедленному вызову метода onBackup() . Вместо этого диспетчер резервного копирования ждет подходящего времени, а затем выполняет резервное копирование для всех приложений, которые запросили резервное копирование с момента выполнения последнего резервного копирования. На этом этапе вы должны предоставить данные вашего приложения диспетчеру резервного копирования, чтобы их можно было сохранить в облачном хранилище.

Только диспетчер резервного копирования может вызывать метод onBackup() вашего агента резервного копирования. Каждый раз, когда данные вашего приложения изменяются и вы хотите выполнить резервное копирование, вы должны запросить операцию резервного копирования, вызвав dataChanged() . Дополнительную информацию см. в разделе Запрос резервной копии .

Совет . При разработке приложения вы можете инициировать немедленную операцию резервного копирования из диспетчера резервного копирования с помощью инструмента bmgr .

Когда диспетчер резервного копирования вызывает ваш метод onBackup() , он передает три параметра:

oldState
Открытый ParcelFileDescriptor только для чтения, указывающий на последнее состояние резервной копии, предоставленное вашим приложением. Это не данные резервной копии из облачного хранилища, а локальное представление данных, резервная копия которых была создана при последнем вызове onBackup() , как определено newState или onRestore() . onRestore() рассматривается в следующем разделе. Поскольку onBackup() не позволяет вам читать существующие данные резервной копии в облачном хранилище, вы можете использовать это локальное представление, чтобы определить, изменились ли ваши данные с момента последнего резервного копирования.
data
Объект BackupDataOutput , который вы используете для доставки данных резервной копии в диспетчер резервного копирования.
newState
Открытый ParcelFileDescriptor для чтения и записи, указывающий на файл, в который вы должны записать представление данных, которые вы доставили в data . Представление может быть таким же простым, как временная метка последнего изменения вашего файла. Этот объект возвращается как oldState в следующий раз, когда диспетчер резервного копирования вызывает ваш метод onBackup() . Если вы не записываете данные резервной копии в newState , тогда oldState будет указывать на пустой файл в следующий раз, когда Backup Manager вызовет onBackup() .

Используя эти параметры, реализуйте метод onBackup() чтобы сделать следующее:

  1. Проверьте, изменились ли ваши данные с момента последнего резервного копирования, сравнив oldState с текущими данными. То, как вы читаете данные в oldState зависит от того, как вы изначально записали их в newState (см. шаг 3). Самый простой способ записать состояние файла — использовать временную метку последнего изменения. Например, вот как вы можете прочитать и сравнить метку времени из oldState :

    Котлин

    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
    }

    Ява

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

    Если ничего не изменилось и резервное копирование не требуется, перейдите к шагу 3.

  2. Если ваши данные изменились по сравнению с oldState , запишите текущие данные в data чтобы создать их резервную копию в облачном хранилище.

    Вы должны записать каждый фрагмент данных как объект в BackupDataOutput . Сущность — это плоская запись двоичных данных, идентифицируемая уникальной ключевой строкой. Таким образом, набор данных, для которого выполняется резервное копирование, концептуально представляет собой набор пар ключ-значение.

    Чтобы добавить объект в набор резервных данных, необходимо:

    1. Вызовите writeEntityHeader() , передав уникальный строковый ключ для данных, которые вы собираетесь записать, и размер данных.

    2. Вызовите writeEntityData() , передав буфер байтов, содержащий ваши данные, и количество байтов для записи из буфера, которое должно соответствовать размеру, переданному в writeEntityHeader() .

    Например, следующий код объединяет некоторые данные в поток байтов и записывает их в один объект:

    Котлин

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

    Ява

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

    Выполните это для каждого фрагмента данных, резервную копию которого вы хотите создать. То, как вы разделите свои данные на сущности, зависит от вас. Вы можете даже использовать только одну сущность.

  3. Независимо от того, выполняете ли вы резервное копирование (на шаге 2), запишите представление текущих данных в newState ParcelFileDescriptor . Диспетчер резервного копирования сохраняет этот объект локально как представление данных, для которых в данный момент выполняется резервное копирование. Он передает это вам обратно как oldState при следующем вызове onBackup() , чтобы вы могли определить, необходима ли еще одна резервная копия, как это было сделано на шаге 1. Если вы не запишете текущее состояние данных в этот файл, тогда oldState будет пусто во время следующего обратного вызова.

    В следующем примере представление текущих данных сохраняется в newState с использованием временной метки последнего изменения файла:

    Котлин

    val modified = dataFile.lastModified()
    FileOutputStream(newState.fileDescriptor).also {
       DataOutputStream(it).apply {
           writeLong(modified)
       }
    }

    Ява

    FileOutputStream outstream = new FileOutputStream(newState.getFileDescriptor());
    DataOutputStream out = new DataOutputStream(outstream);
    
    long modified = dataFile.lastModified();
    out.writeLong(modified);

Выполнить восстановление

Когда приходит время восстановить данные вашего приложения, диспетчер резервного копирования вызывает метод onRestore() вашего агента резервного копирования. При вызове этого метода диспетчер резервного копирования доставляет данные резервной копии, чтобы вы могли восстановить их на устройстве.

Только диспетчер резервного копирования может вызывать onRestore() , что происходит автоматически, когда система устанавливает ваше приложение и находит существующие данные резервной копии.

Когда диспетчер резервного копирования вызывает ваш метод onRestore() , он передает три параметра:

data
Объект BackupDataInput , который позволяет вам читать данные резервной копии.
appVersionCode
Целое число, представляющее значение атрибута манифеста android:versionCode вашего приложения, каким оно было при резервном копировании этих данных. Вы можете использовать это, чтобы перепроверить текущую версию приложения и определить, совместим ли формат данных. Дополнительные сведения об использовании этого параметра для обработки различных версий данных восстановления см. в разделе Проверка версии данных восстановления .
newState
Открытый ParcelFileDescriptor для чтения и записи, указывающий на файл, в который вы должны записать окончательное состояние резервной копии, предоставленное с data . Этот объект возвращается как oldState при следующем вызове onBackup() . Напомним, что вы также должны написать тот же объект newState в обратном вызове onBackup() — это также гарантирует, что объект oldState , переданный в onBackup() , действителен даже при первом вызове onBackup() после восстановления устройства.

В вашей реализации onRestore() вы должны вызвать readNextHeader() для data , чтобы перебрать все объекты в наборе данных. Для каждой найденной сущности выполните следующие действия:

  1. Получите ключ объекта с помощью getKey() .
  2. Сравните ключ сущности со списком известных значений ключей, которые вы должны были объявить как статические конечные строки внутри класса BackupAgent . Когда ключ соответствует одной из известных строк ключа, введите оператор, чтобы извлечь данные объекта и сохранить их на устройстве:

    1. Получите размер данных объекта с помощью getDataSize() и создайте массив байтов этого размера.
    2. Вызовите readEntityData() и передайте ему массив байтов, куда будут помещены данные, а также укажите начальное смещение и размер для чтения.
    3. Ваш массив байтов теперь заполнен. Считайте данные и запишите их на устройство так, как вам нравится.
  3. После того, как вы прочитаете и запишете свои данные обратно на устройство, запишите состояние ваших данных в параметр newState так же, как вы это делаете во время onBackup() .

Например, вот как можно восстановить данные из резервной копии, приведенной в примере из предыдущего раздела:

Котлин

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

Ява

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

В этом примере параметр appVersionCode , переданный в onRestore() не используется. Однако вы можете использовать его, если вы решили выполнить резервное копирование, когда версия приложения пользователя фактически изменилась (например, пользователь перешел с версии 1.5 вашего приложения на 1.0). Дополнительную информацию см. в следующем разделе.

Проверьте версию данных восстановления

Когда диспетчер резервного копирования сохраняет ваши данные в облачное хранилище, он автоматически включает версию вашего приложения, определенную атрибутом android:versionCode вашего файла манифеста. Прежде чем диспетчер резервного копирования вызовет ваш агент резервного копирования для восстановления данных, он просматривает android:versionCode установленного приложения и сравнивает его со значением, записанным в наборе данных восстановления. Если версия, записанная в наборе данных для восстановления, новее, чем версия приложения на устройстве, значит, пользователь понизил версию своего приложения. В этом случае диспетчер резервного копирования прервет операцию восстановления вашего приложения и не вызовет метод onRestore() , поскольку набор восстановления считается бессмысленным для более старой версии.

Вы можете переопределить это поведение с помощью атрибута android:restoreAnyVersion . Установите для этого атрибута значение true , чтобы указать, что вы хотите восстановить приложение независимо от версии набора восстановления. Значение по умолчанию — false . Если вы установите для этого параметра значение true , диспетчер резервного копирования будет игнорировать android:versionCode и во всех случаях вызывать ваш метод onRestore() . При этом вы можете вручную проверить разницу версий в методе onRestore() и предпринять любые шаги, необходимые для обеспечения совместимости данных, если версии не совпадают.

Чтобы помочь вам обрабатывать различные версии во время операции восстановления, метод onRestore() передает вам код версии, включенный в набор данных восстановления, в качестве параметра appVersionCode . Затем вы можете запросить код версии текущего приложения с помощью поля PackageInfo.versionCode . Например:

Котлин

val info: PackageInfo? = try {
    packageManager.getPackageInfo(packageName, 0)
} catch (e: PackageManager.NameNotFoundException) {
    null
}

val version: Int = info?.versionCode ?: 0

Ява

PackageInfo info;
try {
    String name = getPackageName();
    info = getPackageManager().getPackageInfo(name, 0);
} catch (NameNotFoundException nnfe) {
    info = null;
}

int version;
if (info != null) {
    version = info.versionCode;
}

Затем сравните version , полученную из PackageInfo с appVersionCode переданным в onRestore() .

Запросить резервную копию

Вы можете запросить операцию резервного копирования в любое время, вызвав dataChanged() . Этот метод уведомляет диспетчер резервного копирования о том, что вы хотите выполнить резервное копирование данных с помощью агента резервного копирования. Затем диспетчер резервного копирования вызывает метод onBackup() вашего агента резервного копирования в будущем. Как правило, вам следует запрашивать резервное копирование каждый раз, когда ваши данные изменяются (например, когда пользователь меняет настройки приложения, резервную копию которого вы хотите создать). Если вы вызываете dataChanged() несколько раз, прежде чем диспетчер резервного копирования запросит резервную копию у вашего агента, ваш агент все равно получит только один вызов onBackup() .

Запросить восстановление

В ходе нормальной работы вашего приложения вам не нужно запрашивать операцию восстановления. Система автоматически проверяет наличие резервных данных и выполняет восстановление после установки вашего приложения.

Перейти на автоматическое резервное копирование

Вы можете перевести свое приложение на полное резервное копирование данных, установив android:fullBackupOnly значение true в элементе <application> файла манифеста. При работе на устройстве с Android 5.1 (уровень API 22) или более ранней версии ваше приложение игнорирует это значение в манифесте и продолжает выполнять резервное копирование значений ключа. При работе на устройстве с Android 6.0 (уровень API 23) или выше ваше приложение выполняет автоматическое резервное копирование вместо резервного копирования ключей и значений.

Конфиденциальность пользователя

Мы в Google прекрасно осознаем доверие, которое оказывают нам пользователи, и нашу ответственность за защиту конфиденциальности пользователей. Google безопасно передает данные резервного копирования на серверы Google и обратно, чтобы обеспечить функции резервного копирования и восстановления. Google рассматривает эти данные как личную информацию в соответствии с Политикой конфиденциальности Google.

Кроме того, пользователи могут отключить функцию резервного копирования данных через настройки резервного копирования системы Android. Когда пользователь отключает резервное копирование, служба резервного копирования Android удаляет все сохраненные данные резервного копирования. Пользователь может повторно включить резервное копирование на устройстве, но служба резервного копирования Android не будет восстанавливать ранее удаленные данные.