android Data Backup(下)

执行恢复

恢复程序数据时,备份管理器将调用备份代理的onRestore()方法。调用此方法时,备份管理器会把备份的数据传入,以供恢复到设备中去。

只有备份服务器能够调用onRestore(),在系统安装应用程序并且发现有备份数据存在时,调用会自动发生。不过,也可以通过调用requestRestore()来发起恢复数据的请求(详情参阅请求恢复)。

: 在开发应用程序的过程中,可以用bmgr工具发起恢复数据的请求。

当备份管理器调用onRestore() 方法时,传入以下三个参数:

data

BackupDataInput对象,用以读取备份数据。

appVersionCode

整数,表示备份数据时应用程序manifest中的android:versionCode属性。可以用于核对当前程序版本并确定数据格式的兼容性。关于利用此版本号来处理不同版本恢复数据的详细情况,请参阅下文检查恢复数据的版本

newState

已打开的,可读写的文件描述符ParcelFileDescriptor,指向一个文件,这里必须写入最后一次提交data数据的备份状态。本对象在下次调用onBackup()时作为oldState 返回。回想一下,onBackup()方法也必须写入newState 对象——这里也同样要这么做。这样即使设备重置后第一次调用onBackup(),也能确保有可用的oldState对象能传给onBackup()方法。

在实现onRestore()时,应该对data 调用readNextHeader(),以遍历数据集里所有的entity。对其中每个entity须进行以下操作:

1. getKey()获取entity的键值。

2. 将此entity键值和已知键值清单进行比较,这个清单应该已经在BackupAgent继承类中作为字符串常量(staticfinal string)进行定义。一旦键值匹配其中一个键,就执行读取entity数据并保存到设备的语句:

1. getDataSize()读取entity数据大小并据其创建字节数组。

2. 调用readEntityData() ,传入字节数组作为获取数据的缓冲区,并指定起始位置和读取字节数。

3. 字节数组将被填入数据,按需读取数据并写入设备即可。

3. 把数据读出并写回设备以后,和上面onBackup()过程类似,把数据的状态写入newState 参数。

下面是把前一节例子中所备份的数据进行恢复的示例:

@Override 

public void onRestore(BackupDataInput data, int appVersionCode, 

                  ParcelFileDescriptor newState) throws IOException { 

   // 应该是只有一个entity

   // 但最安全的方法还是用循环来处理

   while (data.readNextHeader()) { 

       String key = data.getKey(); 

       int dataSize = data.getDataSize(); 

 

       // 如果键值是所需的(保存TopScore),注意这个键值是用于

       // 写入备份entityheader 

       if (TOPSCORE_BACKUP_KEY.equals(key)) { 

          // BackupDataInput创建输入流

          byte[] dataBuf = new byte[dataSize]; 

          data.readEntityData(dataBuf, 0, dataSize); 

          ByteArrayInputStream baStream = new ByteArrayInputStream(dataBuf); 

          DataInputStream in = new DataInputStream(baStream); 

 

          // 从备份数据中读取playernamescore

          mPlayerName = in.readUTF(); 

          mPlayerScore = in.readInt(); 

 

          //Record the score on the device (to a file orsomething) 

          recordScore(mPlayerName, mPlayerScore); 

       } else { 

          // 不知道这个entity键值,跳过,(这本不该发生)

          data.skipEntityData(); 

       } 

   } 

 

   //Finally, write to the state blob (newState) that describes therestored 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创建备份代理来实现。因为不必实现onBackup()onRestore()了,BackupAgentHelper 创建备份代理所需的代码量将远远少于BackupAgent

BackupAgentHelper 的实现必须要使用一个或多个backuphelperbackuphelper是一种专用组件,BackupAgentHelper 用它来对特定类型的数据执行备份和恢复操作。Android框架目前提供两种helpers

·       SharedPreferencesBackupHelper用于备份SharedPreferences文件。

·       FileBackupHelper 用于备份来自内部存储的文件。

BackupAgentHelper中可包含多个helper,但对于每种数据类型只需用到一个helper 。也就是说,即使存在多个SharedPreferences 文件,也只需要一个SharedPreferencesBackupHelper

对于每个要加入BackupAgentHelperhelper,必须在onCreate() 中执行以下步骤:

1.         实例化所需的helper。在其构造方法里必须指定需备份的文件。

2.         调用addHelper() helper加入BackupAgentHelper

下一节描述了如何使用每种helper创建备份代理。

 

备份SharedPreferences

实例化SharedPreferencesBackupHelper时,必须包括一个或多个SharedPreferences 文件。

例如,假设需备份的SharedPreferences文件名为“user_preferences”,完整的使用BackupAgentHelper的备份代理代码类似如下:

public class MyPrefsBackupAgent extends BackupAgentHelper { 

   //SharedPreferences 文件名

   static final String PREFS = "user_preferences"; 

 

   // 唯一标识备份数据的键值

   static final String PREFS_BACKUP_KEY = "prefs"; 

 

   // 申请helper并加入备份代理

   @Override 

   public void onCreate() { 

       SharedPreferencesBackupHelper helper = new SharedPreferencesBackupHelper(this, PREFS); 

       addHelper(PREFS_BACKUP_KEY, helper); 

   } 

}

好,这就是一个备份代理的完整实现。SharedPreferencesBackupHelper内含了备份和恢复SharedPreferences文件的所有代码。

当备份管理器调用onBackup() onRestore()时,BackupAgentHelper 调用helper来对给定文件执行备份和恢复操作。

注: SharedPreferences 是线程安全的,因此可以从备份代理和其它activity中安全地读写sharedpreferences文件。

 

备份其它文件

在实例化FileBackupHelper时,必须包含一个或多个保存于程序内部存储中的文件名称。(路径的描述方式类似getFilesDir(),并且作为openFileOutput() 写入文件的路径。)

比如,需要备份两个名为“scores”“stats”的文件,备份代理使用BackupAgentHelper 示例如下:

public class MyFileBackupAgent extends BackupAgentHelper { 

   //SharedPreferences文件的名称

   static final String TOP_SCORES = "scores"; 

   static final String PLAYER_STATS = "stats"; 

 

   // 唯一标识备份数据集的键值

   static final String FILES_BACKUP_KEY = "myfiles"; 

 

   // 申请helper并加入备份代理

   void onCreate() { 

       FileBackupHelper helper = new FileBackupHelper(this, TOP_SCORES, PLAYER_STATS); 

       addHelper(FILES_BACKUP_KEY, helper); 

   } 

}

FileBackupHelper 包含了备份和恢复存于内部存储的文件所需的全部代码。

但是,读写内部存储文件不是线程安全的。要确保activity操作文件的时候备份代理不会去读写文件,每次读写文件时必须使用同步语句。比如,Activity读写文件时,需要用一个对象作为同步语句的内部锁。

// 内部锁对象

static final Object[] sDataLock = new Object[0];

有趣的事实:长度为零的数组要比普通对象更轻量化,因此用作内部锁会是个好主意。

然后,每次读写文件时用这个锁创建同步语句。以下是把游戏分数写入文件的同步语句示例:

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, "Unableto write to file"); 

}

应该用同一个锁同步读取文件的语句。

然后,在BackupAgentHelper内,必须覆盖onBackup()onRestore()方法,用同一个内部锁同步备份和恢复操作。比如,上例中MyFileBackupAgent需要以下方法:

@Override 

public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, 

        ParcelFileDescriptor newState) throws IOException { 

   //Hold the lock while the FileBackupHelper performsbackup 

   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 thefile 

   synchronized (MyActivity.sDataLock) { 

       super.onRestore(data, appVersionCode, newState); 

   } 

}

好了,所有要做的工作仅仅是在onCreate()方法内加入FileBackupHelper,覆盖onBackup()onRestore() 并同步读写。

关于用FileBackupHelper实现BackupAgentHelper的例子,请参阅例程备份和恢复中的FileHelperExampleAgent 类。

 

 

检查恢复数据的版本

在把数据保存到云存储中去时,备份管理器会自动包含应用程序的版本号,版本号是在manifest文件的android:versionCode 属性中定义的。在调用备份代理恢复数据之前,备份管理器会查询已安装程序的android:versionCode并与记录在备份数据中的版本号相比较。如果备份数据的版本比设备上的要,则意味着用户安装了旧版本的程序。这时备份管理器将停止恢复操作,onRestore()方法也不会被调用,因为把数据恢复给旧版本的程序是没有意义的。

android:restoreAnyVersion属性可以取代以上规则。此属性用truefalse标明是否在恢复时忽略数据集的版本,默认值是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 中的version 和传入onRestore()appVersionCode 即可。

警告:请确认已经理解了android:restoreAnyVersion 设为true的后果。如果不是所有版本的程序都能在onRestore()时正确解析数据格式的差异,那么保存到设备上的数据格式可能会与已安装的版本不兼容。

 

 

请求备份

任何时候都可以通过调用dataChanged()来发起备份请求。此方法通知备份管理器用备份代理来备份数据。然后,备份管理器将会适时调用备份代理的onBackup()方法。通常每次数据发生变化时都应该请求备份数据(比如用户修改了需保存的程序配置)。如果在备份管理器实际执行前连续调用了dataChanged()很多次,代理仅会执行一次onBackup()

注: 在程序开发过程中,可以用bmgrtool发起备份请求,备份将会立即执行。

 

请求恢复

在程序正常的生命周期内,应该不需要发起恢复数据的请求。在程序安装完成时,系统会自动检查备份数据并执行恢复操作。不过必要时,也可以通过调用requestRestore()来人工发起恢复数据的请求。这时,备份管理器会调用onRestore(),并把现有备份数据集作为数据传入该方法。

注:在程序开发过程中,可以用bmgrtool发起恢复数据的请求。

 

 

测试备份代理

一旦实现了备份代理,就可以用bmgr按以下步骤测试备份和恢复功能了:

1. 在合适的Android系统镜像上安装应用程序

o    如果使用仿真器,须创建和使用Android2.2API Level8)以上版本的AVD

o    如果使用硬件设备,则此设备必须运行Android2.2以上版本并内置AndroidMarket功能。

2. 确保启用备份功能

o    如果使用仿真器,可以SDK tools/路径下用以下命令启用备份功能:

adb shell bmgr enable true

o    如果使用硬件设备,则在系统Settings 选择Privacy,启用Backup my data Automaticrestore

3. 运行程序并初始化一些数据。

如果程序已经正确地实现了备份代码,那每次数据变化时都会请求备份。例如,每当用户改变了一些数据,程序将调用dataChanged(),这就往备份管理器的请求队列里加入了一个备份请求。出于测试的目的,也可以用以下bmgr命令发起一个请求:

adb shell bmgrbackup your.package.name

4. 执行备份操作:

adb shell bmgr run

这一步强迫备份管理器执行所有已入队列的备份请求。

5. 卸载应用程序:

adbuninstall your.package.name

6. 重新安装应用程序。

如果备份代理成功运行,那第4步里备份的数据将会被恢复。

 

补充

         文章精选

                  [IBM]基于 android 数据备份恢复的一种实现

你可能感兴趣的:(android Data Backup(下))