Резервное копирование пар "ключ-значение" с помощью Android Backup Service

Android Backup Service позволяет создавать резервные копии данных типа "ключ-значение" в облачном хранилище и восстанавливать их в приложении Android. При создании резервной копии данных типа "ключ-значение" данные резервной копии приложения передаются в транспорт резервного копирования устройства. Если на устройстве используется стандартный транспорт резервного копирования Google, данные передаются в Android Backup Service для архивации.

Объем данных ограничен 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 Backup Service больше не требуется ключ сервиса, но некоторые старые устройства могут по-прежнему проверять его наличие при резервном копировании. Задайте для параметра android:name значение com.google.android.backup.api_key, а для параметра android:value – значение unused.

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

Расширение BackupAgentHelper

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

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

  • SharedPreferencesBackupHelper для резервного копирования SharedPreferences файлов.
  • FileBackupHelper, чтобы создать резервные копии файлов из внутренней памяти.

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

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

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

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

Резервное копирование SharedPreferences

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

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

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

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

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

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

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

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

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

Kotlin

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

Java

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

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

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

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

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

Kotlin

@Throws(IOException::class)
override fun onBackup(
        oldState: ParcelFileDescriptor,
        data: BackupDataOutput,
        newState: ParcelFileDescriptor
) {
    // Hold the lock while the FileBackupHelper performs back up
    synchronized(MyActivity.sDataLock) {
        super.onBackup(oldState, data, newState)
    }
}

@Throws(IOException::class)
override fun onRestore(
        data: BackupDataInput,
        appVersionCode: Int,
        newState: ParcelFileDescriptor
) {
    // Hold the lock while the FileBackupHelper restores the file
    synchronized(MyActivity.sDataLock) {
        super.onRestore(data, appVersionCode, newState)
    }
}

Java

@Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
          ParcelFileDescriptor newState) throws IOException {
    // Hold the lock while the FileBackupHelper performs back up
    synchronized (MyActivity.sDataLock) {
        super.onBackup(oldState, data, newState);
    }
}

@Override
public void onRestore(BackupDataInput data, int appVersionCode,
        ParcelFileDescriptor newState) throws IOException {
    // Hold the lock while the FileBackupHelper restores the file
    synchronized (MyActivity.sDataLock) {
        super.onRestore(data, appVersionCode, newState);
    }
}

Расширение BackupAgent

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

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

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

Обязательные методы

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

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

Менеджер резервного копирования вызывает этот метод во время операции восстановления. Этот метод передает резервные данные, которые приложение может использовать для восстановления своего прежнего состояния, как описано в разделе Выполнение восстановления.

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

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

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

Только Backup Manager может вызывать метод onBackup() вашего агента резервного копирования. Каждый раз, когда данные приложения меняются и вы хотите создать резервную копию, необходимо запрашивать операцию резервного копирования, вызывая dataChanged(). Подробнее о том, как запросить резервную копию…

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

Когда Backup Manager вызывает метод onBackup(), он передает три параметра:

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

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

  1. Проверьте, изменились ли ваши данные с момента последнего резервного копирования, сравнив oldState с текущими данными. Способ чтения данных в oldState зависит от того, как вы изначально записали их в newState (см. шаг 3). Проще всего зафиксировать состояние файла с помощью временной метки последнего изменения. Например, вот как можно прочитать и сравнить временную метку из 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
    }

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

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

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

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

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

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

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

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

    Повторите это действие для всех данных, которые хотите скопировать. Как вы разделите свои данные на сущности, зависит от вас. Вы можете использовать даже один объект.

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

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

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

Как восстановить данные

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

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

Когда Backup Manager вызывает метод 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().

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

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

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

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

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

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

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

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

Затем сравните значение 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.

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