由于Android 2.2(API 8),Android已经提供了Key / Value Backup功能,可以让开发人员将应用数据备份到云端。 密钥/值备份功能(以前称为备份API和Android备份服务)通过将应用数据上传到Android备份服务来保留应用数据。 数据量限制在每个应用程序用户5MB,并且不收取存储备份数据的费用。
一、与自动备份的比较
像自动备份一样,安装应用程序时,系统会自动恢复密钥/值备份。 下表介绍了键/值备份与自动备份的一些主要区别:
键/值备份 | 自动备份 |
---|---|
适用于API 8,Android 2.2 | 可用于API 23,Android 6.0 |
应用程序必须实现一个BackupAgent。 备份代理定义要备份的数据以及如何还原数据。 | 默认情况下,自动备份几乎包含所有应用程序的文件。 您可以使用XML来包含和排除文件。 在引擎盖下,自动备份依赖于捆绑在SDK中的备份代理。 |
当有数据准备好备份时,应用程序必须发出请求。 来自多个应用程序的请求每隔几个小时进行批量和执行。 | 备份大概每天自动发生一次。 |
备份数据可以通过wifi或蜂窝数据传输。 | 备份数据只能通过wifi传输。 如果设备从未连接到WiFi网络,则永远不会出现自动备份。 |
应用程序在备份期间不会关闭。 | 系统会在备份期间关闭应用程序。 |
备份数据存储在Android备份服务中,限制为每个应用程序5MB。 | 备份数据存储在用户的Google云端硬盘限制为每个应用程序25MB。 |
相关的API方法不是基于: | 相关的API方法是基于: |
二、实现键/值备份
要备份应用程序数据,您需要实现备份代理。 您的备份代理在备份和还原期间都由备份管理器调用。
要实施备份代理,您必须:
1、使用android:backupAgent属性在您的清单文件中声明备份代理。
2、使用Android备份服务注册您的应用程序。
3、通过以下两种方式定义备份代理:
1)、扩展BackupAgent:BackupAgent类提供了应用程序与备份管理器进行通信的中央接口。 如果您直接扩展此类,则必须覆盖onBackup()和onRestore()来处理数据的备份和恢复操作。
2)、扩展BackupAgentHelper:BackupAgentHelper类为BackupAgent类提供了一个方便的包装,最大限度地减少了您需要编写的代码量。 在您的BackupAgentHelper中,您必须使用一个或多个“辅助”对象,这些对象可自动备份和还原某些类型的数据,以便您不需要实现onBackup()和onRestore()。
Android目前提供备份助手,可以从SharedPreferences和内部存储中备份和还原完整的文件。
三、在清单中声明备份代理
这是最简单的步骤,因此一旦您决定了备用代理的类名称,就可以在您的清单中使用<application>标签中的android:backupAgent属性声明它。
例如:
<manifest ... >
...
<application android:label="MyApplication"
android:backupAgent="MyBackupAgent">
<activity ... >
...
</activity>
</application>
</manifest>
您可能想要使用的另一个属性是android:restoreAnyVersion。 此属性使用布尔值来指示是否要恢复应用程序数据,而不管当前应用程序版本与生成备份数据的版本相比。 (默认值为“false”。)有关详细信息,请参阅检查还原数据版本。
注意:您必须使用的备份服务和API仅在运行API Level 8(Android 2.2)或更高版本的设备上可用,因此您还应将android:minSdkVersion属性设置为“8”。
四、注册Android备份服务
Google为运行Android 2.2或更高版本的大多数Android设备提供备用传输和Android备份服务。
为了使您的应用程序使用Android备份服务执行备份,您必须使用该服务注册应用程序才能接收备份服务密钥,然后在您的Android清单中声明备份服务密钥。
要获取备份服务密钥,请注册Android备份服务。 注册时,您将获得备用服务密钥和适用于您的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注册接收到的备份服务密钥。
如果您有多个应用程序,则必须使用相应的软件包名称注册每个应用程序。
注意:由Android Backup Service提供的备份传输不能保证在支持备份的所有Android设备上都可用。 某些设备可能支持使用不同的传输进行备份,有些设备根本不支持备份,您的应用程序无法知道设备上使用什么传输。 但是,如果您为应用程序实施备份,则应始终包含用于Android备份服务的备份服务密钥,以便您的应用程序在设备使用Android备份服务传输时执行备份。 如果设备不使用Android备份服务,那么具有备份服务密钥的<meta-data>元素将被忽略。
五、扩展BackupAgent
大多数应用程序不应该直接扩展BackupAgent类,而应该扩展BackupAgentHelper以利用自动备份和还原文件的内置助手类。 但是,如果需要,可以直接扩展BackupAgent:
1、您数据格式的版本。 例如,如果您预计需要修改编写应用程序数据的格式,则可以在还原操作期间构建备份代理以交叉检查应用程序版本,如果设备上的版本为与备份数据不同。 有关详细信息,请参阅检查还原数据版本。
2、您可以指定应备份的数据部分,以及每个部分如何还原到设备, 而不是备份整个文件。(这也可以帮助您管理不同的版本,因为您将数据作为唯一实体读取和写入,而不是完整的文件。)
3、备份数据库中的数据。 如果您有一个要在用户重新安装应用程序时还原的SQLite数据库,则需要构建一个自定义的BackupAgent,在备份操作期间读取相应的数据,然后创建表并在还原操作期间插入数据。
如果您不需要执行上述任何任务,并希望从SharedPreferences或内部存储中备份完整的文件,则应跳过扩展BackupAgentHelper。
一)、必需的方法
通过扩展BackupAgent创建备份代理时,必须实现以下回调方法:
备份管理器在请求备份后调用此方法。 在此方法中,您将从设备中读取应用程序数据,并将要备份的数据传递到备份管理器,如下文“执行备份”中所述。
备份管理器在恢复操作期间调用此方法(您可以请求还原,但系统在用户重新安装应用程序时自动执行还原)。 当调用此方法时,备份管理器会传送备份数据,然后将其还原到设备,如下文“执行恢复”中所述。
二)、执行备份
在备份应用程序数据的时候,备份管理器会调用您的onBackup()方法。 您必须将应用程序数据提供给备份管理器,以便将其保存到云存储。
只有备份管理器可以调用备份代理的onBackup()方法。 每次您的应用程序数据更改并且要执行备份时,必须通过调用dataChanged()来请求备份操作(有关更多信息,请参阅请求备份)。 备份请求不会立即调用onBackup()方法。 相反,备份管理器会等待适当的时间,然后对自执行上次备份以来请求备份的所有应用程序执行备份。
提示:开发应用程序时,可以使用bmgr工具从备份管理器启动即时备份操作。
当备份管理器调用onBackup()方法时,它会传递三个参数:
oldState
一个打开的只读ParcelFileDescriptor,指向应用程序提供的最后一个备份状态。 这不是来自云存储的备份数据,但是最后一次onBackup()备份的数据的本地表示被调用(由newState,下面或从onRestore()定义) - 更多关于这一点在下一节)。 因为onBackup()不允许您读取云存储中的现有备份数据,因此您可以使用此本地表示来确定自上次备份以来数据是否已更改。
data
BackupDataOutput对象,用于将备份数据传递到备份管理器。
newState
一个打开的读/写ParcelFileDescriptor,指向一个文件,您必须在其中写入您传递给数据的数据的表示(一个表示可以像最后修改的文件的时间戳一样简单)。 下一次备份管理器调用onBackup()方法时,此对象将作为oldState返回。 如果您不将备份数据写入newState,那么在Backup Manager调用onBackup()时,oldState将指向一个空文件。
使用这些参数,您应该实现您的onBackup()方法来执行以下操作:
1、通过将oldState与当前数据进行比较,检查自上次备份以来数据是否已更改。 如何在oldState中读取数据取决于您最初如何将其写入newState(请参阅步骤3)。 记录文件状态的最简单方法是使用其最后修改的时间戳。 例如,以下是从oldState读取和比较时间戳的方式:
// 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 = mDataFile.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相比,如果您的数据发生变化,将当前数据写入数据,将其备份到云存储。
您必须将每个数据块写入BackupDataOutput中的“实体”。 实体是由唯一的键字符串标识的扁平二进制数据记录。 因此,您备份的数据集在概念上是一组键值对。
要将实体添加到备份数据集中,您必须:
1)、调用writeEntityHeader(),传递您要写入的数据和数据大小的唯一字符串键。
2)、调用writeEntityData(),传递包含数据的字节缓冲区和从缓冲区写入的字节数(应该与传递给writeEntityHeader()的大小相匹配)。
例如,以下代码将一些数据平坦化为字节流,并将其写入单个实体:
// Create buffer stream and data output stream for our data
ByteArrayOutputStream bufStream = new ByteArrayOutputStream();
DataOutputStream outWriter = new DataOutputStream(bufStream);
// Write structured data
outWriter.writeUTF(mPlayerName);
outWriter.writeInt(mPlayerScore);
// 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中:
FileOutputStream outstream = new FileOutputStream(newState.getFileDescriptor());
DataOutputStream out = new DataOutputStream(outstream);
long modified = mDataFile.lastModified();
out.writeLong(modified);
注意:如果将应用程序数据保存到文件中,请确保在访问文件时使用synchronized语句,以便在应用程序中的活动也在写入文件时备份代理程序不会读取该文件。
三)、执行恢复
在恢复应用程序数据的时候,备份管理器会调用备份代理的onRestore()方法。 调用此方法时,备份管理器会传送备份数据,以便将其还原到设备上。
只有备份管理器可以调用onRestore(),当系统安装应用程序并查找现有的备份数据时,它会自动进行。 但是,您可以通过调用requestRestore()来请求应用程序的还原操作(有关更多信息,请参阅请求恢复)。
注意:在开发应用程序时,您还可以使用bmgr工具请求恢复操作。
当备份管理器调用onRestore()方法时,它会传递三个参数:
data
一个BackupDataInput,它允许您读取备份数据。
appVersionCode
一个整数,表示应用程序的android:versionCode清单属性的值,就像备份此数据时一样。 您可以使用它来交叉检查当前的应用程序版本,并确定数据格式是否兼容。 有关使用它来处理不同版本的还原数据的更多信息,请参阅下面有关检查还原数据版本的部分。
newState
一个打开,读/写ParcelFileDescriptor,指向一个文件,您必须在其中写入随数据提供的最终备份状态。 此对象将在下次onBackup()被调用时作为oldState返回。 回想一下,您还必须在onBackup()回调中编写相同的newState对象 - 也可以在此处确保在恢复设备后第一次调用onBackup()时,给予onBackup()的oldState对象有效。
在执行onRestore()时,应该调用readNextHeader()对数据进行遍历数据集中的所有实体。 对于找到的每个实体,请执行以下操作:
1、用getKey()获取实体密钥。
2、将实体密钥与您应该在BackupAgent类中声明为静态最终字符串的已知键值列表进行比较。 当密钥与您已知的密钥字符串匹配时,输入语句以提取实体数据并将其保存到设备:
1)、使用getDataSize()获取实体数据大小,并创建一个大小的字节数组。
2)、调用readEntityData()并传递字节数组,这是数据将要去的位置,并指定起始偏移量和要读取的大小。
3)、您的字节数组现在已满,您可以读取数据并将其写入设备,只要您喜欢。
3、读取数据并将其写入设备后,将数据的状态写入newState参数,与onBackup()中的操作相同。
例如,以下是恢复上一节中的示例备份的数据的方法:
@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
mPlayerName = in.readUTF();
mPlayerScore = in.readInt();
// Record the score on the device (to a file or something)
recordScore(mPlayerName, mPlayerScore);
} 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(mPlayerName);
out.writeInt(mPlayerScore);
}
在这个例子中,没有使用传递给onRestore()的appVersionCode参数。 但是,如果您在应用程序的用户版本实际上向后移动时选择执行备份(例如,用户从您的应用程序的1.5版本转到1.0),则可能需要使用它。 有关详细信息,请参阅有关检查还原数据版本的部分。
有关BackupAgent的示例实现,请参阅备份和还原示例应用程序中的ExampleAgent类。
六、扩展BackupAgentHelper
如果要备份完整的文件(来自SharedPreferences或内部存储),则应使用BackupAgentHelper构建备份代理。 使用BackupAgentHelper构建备份代理所需的代码远远少于扩展BackupAgent,因为您不必实现onBackup()和onRestore()。
BackupAgentHelper的实现必须使用一个或多个备份助手。 备份助手是BackupAgentHelper召集的专门组件,用于对特定类型的数据执行备份和恢复操作。 Android框架目前提供了两个不同的助手:
1、SharedPreferencesBackupHelper备份SharedPreferences文件。
2、FileBackupHelper从内部存储备份文件。
您可以在BackupAgentHelper中包含多个助手,但每个数据类型只需要一个帮助器。 也就是说,如果您有多个SharedPreferences文件,则只需要一个SharedPreferencesBackupHelper。
对于您要添加到BackupAgentHelper的每个帮助器,您必须在您的onCreate()方法期间执行以下操作:
1、实例化所需的助手类。 在类构造函数中,必须指定要备份的适当文件。
2、调用addHelper()将助手添加到您的BackupAgentHelper。
以下部分介绍如何使用每个可用的助手创建备份代理
一)、备份SharedPreferences
当您实例化一个SharedPreferencesBackupHelper时,您必须包含一个或多个SharedPreferences文件的名称。
例如,要备份名为“user_preferences”的SharedPreferences文件,使用BackupAgentHelper的完整备份代理如下所示:
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的方法是线程安全的,因此您可以从备份代理和其他活动安全地读取和写入共享的首选项文件。
二)、备份其他文件
实例化FileBackupHelper时,必须包含保存在应用程序内部存储器中的一个或多个文件的名称(由getFilesDir()指定,这与openFileOutput()写入文件的位置相同)。
例如,要备份名为“分数”和“统计信息”的两个文件,使用BackupAgentHelper的备份代理如下所示:
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包括备份和还原保存到应用程序内部存储器的文件所需的所有代码。
但是,读取和写入内部存储上的文件不是线程安全的。 为确保您的备份代理不会在您的活动同时读取或写入文件,您必须在每次执行读取或写入时使用synchronized语句。 例如,在您读取和写入文件的任何活动中,您需要一个对象作为synchronized语句的内在锁:
// Object for intrinsic lock
static final Object sDataLock = new Object();
然后每次读取或写入文件时,使用此锁定创建一个synchronized语句。 例如,这是一个同步语句,用于将游戏中的最新分数写入文件:
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");
}
您应该将read语句与同一个锁同步。
然后,在您的BackupAgentHelper中,必须重写onBackup()和onRestore()以使具有相同内部锁的备份和恢复操作同步。 例如,上面的MyFileBackupAgent示例需要以下方法:
@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);
}
}
所有您需要做的是在onCreate()方法中添加您的FileBackupHelper,并覆盖onBackup()和onRestore()来同步读取和写入操作。
有关使用FileBackupHelper的BackupAgentHelper示例实现,请参阅“备份和还原”示例应用程序中的FileHelperExampleAgent类。
七、检查恢复数据版本
当备份管理器将您的数据保存到云存储时,它会自动包含您的清单文件的android:versionCode属性定义的应用程序版本。 在备份管理器调用备份代理恢复数据之前,请查看已安装应用程序的android:versionCode,并将其与恢复数据集中记录的值进行比较。 如果恢复数据集中记录的版本比设备上的应用程序版本更新,则用户已降级其应用程序。 在这种情况下,备份管理器将中止您的应用程序的还原操作,而不会调用您的onRestore()方法,因为恢复集对于旧版本而言是无意义的。
您可以使用android:restoreAnyVersion属性覆盖此行为。 此属性为“true”或“false”,表示是否要还原应用程序,而不管还原集版本如何。 默认值为“false”。 如果将其定义为“true”,则备份管理器将忽略所有情况下的android:versionCode并调用onRestore()方法。 在这样做时,您可以手动检查您的onRestore()方法中的版本差异,并采取必要步骤使数据兼容,如果版本冲突。
为了帮助您在还原操作期间处理不同的版本,onRestore()方法会将包含在还原数据集中的版本代码传递给appVersionCode参数。 然后,您可以使用PackageInfo.versionCode字段查询当前应用程序的版本代码。 例如:
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获取的版本与传递给onRestore()的appVersionCode进行比较。
注意:确保您了解将应用程序的android:restoreAnyVersion设置为“true”的后果。 如果支持备份的每个版本的应用程序在onRestore()期间不能正确解释数据格式的变化,则设备上的数据可能会以与设备上当前安装的版本不兼容的格式进行保存。
八、请求备份
您可以随时通过调用dataChanged()来请求备份操作。 此方法通知备份管理器您要使用备份代理备份数据。 然后,备份管理器将来会在适当的时候调用备份代理的onBackup()方法。 通常,每次数据更改时,您都应该请求备份(例如用户更改要备份的应用程序首选项)。 如果您连续多次调用dataChanged(),在备份管理器从代理程序请求备份之前,您的代理仍然只收到一个onBackup()的调用。
注意:在开发应用程序时,您可以请求备份,并使用bmgr工具启动即时备份操作。
九、迁移到自动备份
您可以通过在清单文件的<application>元素中将android:fullBackupOnly设置为true来将应用程序转换为全数据备份。 在Android 5.1(API级别22)或更低版本的设备上运行时,您的应用会忽略清单中的该值,并继续执行键/值备份。 在Android 6.0(API级别23)或更高版本的设备上运行时,您的应用会执行自动备份,而不是键/值备份。