使用 Android Backup Service 备份键值对

Android Backup Service 可为 Android 应用中的键值对数据提供云端存储空间备份和恢复。在键值对备份操作执行过程中,应用的备份数据会传递到设备的备份传输中。如果设备使用的是默认 Google 备份传输,则数据会传递到 Android Backup Service 进行归档。

数据量上限为应用的每位用户 5MB,并且存储备份数据是免费的。

如需简要了解 Android 备份选项并获得有关您应该备份和恢复哪些数据的指南,请参阅数据备份概览

注意:对于大多数应用来说,应使用自动备份来实现备份和恢复。您的应用只能实现自动备份和键值对备份中的一种,而不能同时实现这两种备份。自动备份无需代码,会备份整个文件,而键值对备份需要您编写代码,以键值对的形式明确定义备份内容。

实现键值对备份

如需备份您的应用数据,您需要实现备份代理。在备份和恢复期间,备份管理器会调用您的备份代理。

如需实现备份代理,您必须:

  1. 使用 android:backupAgent 属性在清单文件中声明备份代理。
  2. Android Backup Service 注册您的应用。
  3. 通过以下任一方式定义备份代理:
    1. 扩展 BackupAgent

      BackupAgent 类提供一个中央接口,您的应用通过该接口与备份管理器进行通信。如果您直接扩展此类,则必须替换 onBackup()onRestore() 才能处理数据的备份和恢复操作。

    2. 扩展 BackupAgentHelper

      BackupAgentHelper 类为 BackupAgent 类提供了一个便捷的封装容器,可最大限度减少需要编写的代码量。在 BackupAgentHelper 中,您必须使用一个或多个“helper”对象,此类对象可以自动备份和恢复特定类型的数据。这样,您便无需实现 onBackup()onRestore()。除非您需要完全控制应用的备份,否则我们建议您使用 BackupAgentHelper 来处理应用的备份。

      Android 目前提供的备份帮助程序可以备份和恢复 SharedPreferences内部存储空间中的完整文件。

在清单中声明备份代理

这是最简单的步骤,因此,请在确定您的备份代理的类名称后,使用 <application> 标记中的 android:backupAgent 属性在清单中声明备份代理。

例如:

<manifest ... >
    ...
    <application android:label="MyApplication"
                 android:backupAgent="MyBackupAgent">
        <activity ... >
            ...
        </activity>
    </application>
</manifest>

您可能需要使用的另一个属性是 android:restoreAnyVersion。该属性利用布尔值来指示您是否需要恢复应用数据,无论与生成备份数据的应用版本相比,当前应用版本如何。(默认值是“false”。)如需了解详情,请参阅检查恢复数据版本

注册 Android Backup Service

Google 通过 Android Backup Service 为大多数搭载 Android 2.2 或更高版本的 Android 设备提供备份传输。

为了让您的应用使用 Android Backup Service 进行备份,您必须向该服务注册您的应用,以接收 Backup Service 密钥,并在 Android 清单中声明该密钥。

如需获取 Backup Service 密钥,请注册 Android Backup Service。注册时,系统会为您提供一个 Backup Service 密钥和相应的 Android 清单文件 <meta-data> XML 代码,您必须将其包含为 <application> 元素的子项。例如:

<application android:label="MyApplication"
             android:backupAgent="MyBackupAgent">
    ...
    <meta-data android:name="com.google.android.backup.api_key"
        android:value="AEdPqrEAAAAIDaYEVgU6DJnyJdBmU7KLH3kszDXLv_4DIsEIyQ" />
</application>

android:name 必须为 "com.google.android.backup.api_key",而 android:value 必须为注册 Android Backup Service 时接收的 Backup Service 密钥。

如果您有多个应用,必须使用每个应用各自的软件包名称分别注册它们。

注意:不能保证由 Android Backup Service 提供的备份传输可以在支持备份的所有 Android 设备上使用。一些设备可能支持使用其他传输的备份,一些设备可能根本不支持备份,您的应用无法知道设备上使用的是哪种传输。不过,如果您为应用实现了备份,则应始终包含一个 Android Backup Service 的 Backup Service 密钥,以便应用可以在设备使用 Android Backup Service 传输时执行备份操作。如果设备不使用 Android Backup Service,则系统会忽略具有 Backup Service 密钥的 <meta-data> 元素。

扩展 BackupAgentHelper

如果您要备份 SharedPreferences内部存储空间中的完整文件,则应使用 BackupAgentHelper 构建您的备份代理。与扩展 BackupAgent 相比,使用 BackupAgentHelper 构建备份代理所需的代码要少得多,因为您无需实现 onBackup()onRestore()

您的 BackupAgentHelper 实现必须使用一个或多个备份帮助程序。备份帮助程序是一种由 BackupAgentHelper 调用以对特定类型的数据执行备份和恢复操作的专用组件。Android 框架目前提供两种不同的帮助程序:

您可以在 BackupAgentHelper 中添加多个帮助程序,但每种数据类型只需要一个帮助程序。也就是说,如果您有多个 SharedPreferences 文件,则只需要一个 SharedPreferencesBackupHelper

对于您要添加到 BackupAgentHelper 中的每个帮助程序,您必须在 onCreate() 方法中执行以下操作:

  1. 实例化所需帮助程序类的一个实例。在类构造函数中,您必须指定要备份的相应文件。
  2. 调用 addHelper() 以将帮助程序添加到您的 BackupAgentHelper 中。

以下各部分介绍了如何使用每个可用的帮助程序创建备份代理。

备份 SharedPreferences

当您实例化 SharedPreferencesBackupHelper 时,必须包含一个或多个 SharedPreferences 文件的名称。

例如,如需备份名为 user_preferencesSharedPreferences 文件,使用 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 会调用您的备份帮助程序以对指定的文件执行备份和恢复操作。

注意SharedPreferences 的方法具备线程安全性,因此您可以放心地从备份代理和其他 Activity 中读写共享偏好设置文件。

备份其他文件

当您实例化 FileBackupHelper 时,必须包含保存到应用内部存储空间(通过 getFilesDir() 指定,是 openFileOutput() 将文件写入到的同一位置)的一个或多个文件的名称。

例如,如需备份名为 scoresstats 的两个文件,使用 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 包含备份和恢复保存到应用内部存储空间的文件所需的所有代码。

不过,在内部存储空间读取和写入文件不具备线程安全性。为了确保您的备份代理不会与 Activity 同时读取或写入文件,您必须在每次执行读取或写入操作时都使用同步语句。例如,在任何您会读取或写入文件的 Activity 中,您都需要一个用作同步语句的内置锁的对象:

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

以上便是使用 BackupAgentHelper 备份和恢复数据所需的一切。

扩展 BackupAgent

大多数应用不需要直接扩展 BackupAgent 类,而是应扩展 BackupAgentHelper 以利用会自动备份和恢复文件的内置帮助程序类。不过,如果您需要执行以下任务,则可能需要直接扩展 BackupAgent

  • 对数据格式进行版本控制。例如,如果您预计需要修改写入应用数据的格式,则可以构建一个备份代理,以在恢复操作执行期间交叉检查应用版本,并在设备上的版本与生成备份数据的版本不同时完成任何必需的兼容性工作。如需了解详情,请参阅检查恢复数据版本
  • 指定应备份的数据部分以及如何将各个部分恢复到设备,而不是备份整个文件。(这还可以帮助您管理不同的版本,因为您以唯一的实体而不是完整的文件的形式读取和写入数据。)
  • 备份数据库中的数据。如果您有一个 SQLite 数据库,希望在用户重新安装您的应用时恢复它,则需要构建一个自定义 BackupAgent,让它在备份操作执行期间读取相应数据,然后在恢复操作执行期间创建表格并插入数据。

如果您不需要执行上述任何任务,并希望备份 SharedPreferences内部存储空间中的完整文件,请参阅扩展 BackupAgentHelper

必需的方法

创建 BackupAgent 时,您必须实现以下回调方法:

onBackup()
备份管理器会在您请求备份后调用该方法。在该方法中,您会从设备读取应用数据,并将要备份的数据传递到备份管理器,如下文中的执行备份部分所述。
onRestore()
备份管理器会在恢复操作执行期间调用该方法。 该方法会传递您的备份数据,而通过备份数据,您的应用可以恢复之前的状态,如下文中的执行恢复部分所述。

当用户重新安装您的应用时,系统会调用该方法来恢复任何备份数据,但您的应用也可以请求恢复

执行备份

在需要备份应用数据时,备份管理器会调用您的 onBackup() 方法。您必须在该方法中将应用数据提供给备份管理器,以便它可以保存到云端存储空间。

只有备份管理器可以调用备份代理的 onBackup() 方法。每当您的应用数据发生更改且您希望进行备份时,您都必须通过调用 dataChanged() 请求备份操作(如需了解详情,请参阅请求备份)。备份请求不会使备份管理器立即调用您的 onBackup() 方法。相反,备份管理器会等待适当的时间,然后为自上次执行备份以来请求备份的所有应用执行备份操作。

提示:在开发应用时,您可以利用 bmgr 工具通过备份管理器立即启动备份操作。

当备份管理器调用您的 onBackup() 方法时,它会传递以下三个参数:

oldState
一个开放的只读 ParcelFileDescriptor,指向您的应用提供的上次备份状态。这不是云端存储空间中的备份数据,而是上次调用 onBackup() 时备份的数据的本地表示形式(由下文中的 newState 或通过 onRestore() 定义 - 将在下一部分进行详细介绍)。由于 onBackup() 不允许您读取云端存储空间中的现有备份数据,您可以使用此本地表示形式来确定自上次备份以来您的数据是否发生过更改。
data
一个 BackupDataOutput 对象,用于将备份数据传递给备份管理器。
newState
一个开放的读/写 ParcelFileDescriptor,指向一个文件,在该文件中,您必须写入传递给 data 的数据的表示形式(该表示形式可以像文件的上次修改时间戳一样简单)。备份管理器下次调用您的 onBackup() 方法时,系统会将该对象作为 oldState 返回。如果您没有将备份数据写入 newState,则 oldState 会在备份管理器下次调用 onBackup() 时,指向一个空文件。

使用这些参数时,您应实现 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);
    
    

注意:如果您的应用数据保存到了一个文件中,请确保在访问该文件时使用同步语句,以便您的备份代理不会在应用中的 Activity 向该文件写入数据时从中读取数据。

执行恢复

在需要恢复应用数据时,备份管理器会调用备份代理的 onRestore() 方法。备份管理器调用该方法时,会传递您的备份数据,以便您将其恢复到设备上。

只有备份管理器可以调用 onRestore(),当系统安装您的应用并找到现有备份数据时,该调用会自动发生。

注意:在开发应用时,您还可以使用 bmgr 工具请求恢复操作。

当备份管理器调用您的 onRestore() 方法时,它会传递以下三个参数:

data
一个 BackupDataInput,可让您读取备份数据。
appVersionCode
一个整数,表示应用的 android:versionCode 清单属性的值,与备份此数据时它所具有的值一样。您可以使用此参数来交叉检查当前的应用版本,并确定数据格式是否兼容。如需详细了解如何使用此参数处理恢复数据的不同版本,请参阅下文中的检查恢复数据版本部分。
newState
一个开放的读/写 ParcelFileDescriptor,指向一个文件,在该文件中,您必须写入随 data 提供的最终备份状态。下次调用 onBackup() 时,系统会将该对象作为 oldState 返回。我们已经知道,您必须也在 onBackup() 回调中写入同一 newState 对象。在此处也这样做可确保即使在恢复设备后首次调用 onBackup() 时,提供给 onBackup()oldState 对象也是有效的。

在您的 onRestore() 实现中,您应在 data 上调用 readNextHeader(),以遍历数据集中的所有实体。对于找到的每个实体,请执行以下操作:

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

在此示例中,未使用传递到 onRestore()appVersionCode 参数。不过,如果您已选择在用户的应用版本实际回退到之前的版本(例如,用户从您应用的 1.5 版转到 1.0 版)时执行备份,则可能需要使用该参数。如需了解详情,请参阅检查恢复数据版本部分。

检查恢复数据版本

当备份管理器将您的数据保存到云端存储空间时,它会自动包含应用的版本(如清单文件的 android:versionCode 属性所定义)。在备份管理器调用您的备份代理以恢复数据之前,它会查看已安装应用的 android:versionCode,并将其与恢复数据集中记录的值进行比较。如果恢复数据集中记录的版本比设备上的应用版本新,则说明用户已将应用降级。在这种情况下,备份管理器会取消应用的恢复操作,并且不会调用 onRestore() 方法,因为恢复集对较旧版本并无意义。

您可以使用 android:restoreAnyVersion 属性替换此行为。该属性为“true”或“false”,用于指示您是否要恢复应用,而不考虑恢复数据集版本。默认值为“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;
}

之后,只需将从 PackageInfo 获得的 version 与传递到 onRestore()appVersionCode 进行比较。

注意:请确保您了解将应用的 android:restoreAnyVersion 设置为“true”的后果。在 onRestore() 期间,如果您的支持备份的每个应用版本都不适当地考虑数据格式的变化,则设备上的数据可能会以与设备上当前安装的版本不兼容的格式进行保存。

请求备份操作

您可以通过调用 dataChanged() 随时请求备份操作。该方法会通知备份管理器您要使用备份代理备份数据。然后,备份管理器会在未来的适当时间调用备份代理的 onBackup() 方法。通常情况下,每当数据发生更改时(例如当用户更改了您希望备份的应用偏好设置时),您都应请求备份。如果您连续多次调用 dataChanged(),在备份管理器从您的代理请求备份之前,您的代理仍然只会收到一次对 onBackup() 的调用。

注意:在开发应用时,您可以利用 bmgr 工具请求备份并立即启动备份操作。

请求恢复操作

在应用的正常生命周期内,您无需请求恢复操作。系统会自动检查备份数据,并在用户安装您的应用时执行恢复操作。

注意:在开发应用时,您可以使用 bmgr 工具请求恢复操作。

迁移至自动备份

您可以在清单文件的 <application> 元素中将 android:fullBackupOnly 设为 true,从而使应用迁移至完整数据备份。如果您的应用在搭载 Android 5.1(API 级别 22)或更低版本的设备上运行,则会忽略清单中的此值,并继续执行键值对备份。如果您的应用在搭载 Android 6.0(API 级别 23)或更高版本的设备上运行,则会执行自动备份,而不是键值对备份。

用户隐私

在 Google,我们切身体会到用户给予我们的信任,我们会尽职尽责地保护好用户的隐私。Google 会以安全的方式向 Google 服务器以及从中传输备份数据,以提供备份和恢复功能。Google 会根据其隐私权政策将此类数据视为个人信息。

此外,用户还可以通过 Android 系统的备份设置停用数据备份功能。当用户停用备份功能时,Android Backup Service 会删除所有已保存的备份数据。用户可以在设备上重新启用备份功能,但 Android Backup Service 不会恢复以前删除的任何数据。