大多数应用程序不需要直接继承 BackupAgent 类,而是继承 BackupAgentHelper 类,并利用 BackupAgentHelper 内建的 helper 类自动备份和恢复文件。不过,以下三种情况需要直接继承 BackupAgent 类来实现备份代理:
通过继承 BackupAgent 类创建备份代理时,必须实现以下两个方法:
应用程序发出数据备份请求时,备份管理器将调用 onBackup() 方法。在此方法内必须把要备份的数据提供给备份管理器,然后将数据保存到云存储中。
只有备份管理器能够调用备份代理中的 onBackup() 方法。当数据发生改变并需要执行备份时,需要调用 dataChanged() 方法发起备份请求。
备份请求并不会立即导致 onBackup() 方法的调用,备份服务器会等待合适的时机,为上次备份操作后又发出备份请求的所有应用程序执行备份操作。
onBackup() 方法需要传入三个参数,所代表的意义分别如下。
名称 | 作用 |
---|---|
oldState | 表示已打开的、只读的文件描述符 ParcelFileDescriptor,指向应用程序提供的上次备份数据状态的文件。该文件不是来自云存储的备份数据,而是记录上次调用 onBackup() 备份数据相关状态信息的本地文件。onBackup() 不能读取保存于云存储的数据,可以根据此信息来判断数据自上次备份以来是否变动过。 |
Data | BackupDataOutput 对象,用于将要备份的数据传给备份管理器。 |
newState | 表示已打开的、可读写的文件描述符 ParcelFileDescriptor,指向一个用于将提交给 data 参数的数据相关状态信息写入的文件,状态信息可以简单到只是文件的最后修改时间。备份管理器下次调用 onBackup() 时,该对象作为 oldState 传入。若没有向 newState 写入信息,则备份管理器下次调用 onBackup() 时 oldState 将指向一个空文件。 |
利用以上参数可以实现 onBackup(),方法如下:
1)通过比较 oldState,检查自上次备份以来数据是否发生过改变。从 oldState 读取信息的方式取决于当时写入的方式(见步骤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 比较后,如果数据发生了变化,就把当前数据写入 data 以便将其返回并上传到云存储中。必须以 BackupDataOutput 中的“entity“方式写入每一块数据。
一个 entity 是一个二进制数据记录,使用一个唯一的字符串键值进行标识。因此,所备份的数据集实际上是一组键值对。要在备份数据集中增加一个 entity,必须:
下面的示例代码演示了把一些数据拼接为字节流并写入一个 entity 的过程:
// 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);
需要备份的每一块数据都要执行一次该操作。如何将数据切分为 entity 由开发者决定。
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)来访问文件。这样在应用程序的 Activity 进行写文件操作时,备份代理就不会去读文件了。
恢复程序数据时,备份管理器将调用备份代理的 onRestore() 方法。调用此方法时,备份管理器会把在云存储备份的数据传入,以供恢复到设备中去。
只有备份服务器能够调用 onRestore() 方法,在系统安装应用程序并且发现有备份数据存在时,数据恢复操作会自动发生。此外,应用程序也可以通过调用 requestRestore() 方法来发起恢复数据的请求。
当备份管理器调用 onRestore() 方法时,传入以下三个参数。
在实现 onRestore() 时,应该对 data 调用 readNextHeader(),以遍历数据集里所有的 entity。对其中每个 entity 需进行以下操作:
1)用 getKey() 方法获取 entity 的键值。
2)将此 entity 键值和已知键值清单进行比较,这个清单应该已经在 BackupAgent 继承类中作为字符串常量定义。一旦键值匹配其中一个键,就执行读取 entity 数据并保存到设备的操作:
3)把数据读出并写回设备以后,把数据的状态写入 newState 参数。
下面的实例代码将前面的例子中所备份的数据进行了恢复:
@Override
public void onRestore (BackupDatalnput 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 BackupDatalnput
byte[] dataBuf=new byte[dataSize];
data.readEntityData (dataBuf, 0, dataSize) ;
ByteArraylnputStream baStream=new ByteArraylnputStream (dataBuf) ; DatalnputStream in=new DatalnputStream (baStream) ;
// Read the player name and score from the backup data
mPlayerName=in.readUTF(); mPlayerScore=in.readlnt();
// 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.writelnt (mPlayerScore);
}
在以上代码中,传给 onRestore() 的 appVersionCode 参数没有被用到。如果用户程序的版本降低,比如从 1.5 降到 1.0,可能就会用此参数来选择备份数据。