3.从备份中实现恢复
对于上述知识,我们通过一个简单的实例来说明如何实现备份。其实大家都知道,备份是为了以防万一,既然备份了,那么怎么从备份中恢复呢?接下来就对工程稍加改动,从而实现恢复功能,具体步骤如下:
①修改MyBackupAgent类,这里写一些备份数据以便恢复时使用,修改后的代码如下:
public class MyBackupAgent extends BackupAgent { private static final String TAG="MyBackupAgent"; @Override public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) throws IOException { Log.e(TAG,"onBackup running"); ByteArrayOutputStream bufStream=new ByteArrayOutputStream(); DataOutputStream outWrite=new DataOutputStream(bufStream); outWrite.write(1); byte[] buffer=bufStream.toByteArray(); int len =buffer.length; data.writeEntityHeader("DATA",len); data.writeEntityData(buffer,len); } @Override public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) throws IOException { Log.e(TAG,"onRestore running"); } @Override public void onCreate() { super.onCreate(); Log.e(TAG, "onCreate running"); } }
②修改MainActivity文件,添加按钮执行恢复操作,具体代码如下:
public class MainActivity extends Activity { private static final String TAG="MainActivity"; private Button myBackup; private Button restore; private BackupManager backupManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); this.myBackup=(Button)findViewById(R.id.myBackup); this.backupManager=new BackupManager(this); this.myBackup.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { backupManager.dataChanged();//此处执行备份 } }); this.restore=(Button)findViewById(R.id.restore); this.restore.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { backupManager.requestRestore(new RestoreObserver() { @Override public void restoreFinished(int error) { super.restoreFinished(error); Log.e(TAG,"restoreFinished running"); } }); } }); }}
③运行程序,得到下图:
只要按照前面的操作做一遍即可。当完成回复操作后,得到如下日志:
有必要对这个日志进行讲解一下,我们可以看到第1行备份管理器以@pm@为条件检索需要还原的备份数据,这里找到了3个符合条件的数据,分别在2-4这三行。
其次,调用了nextRestorePackage()方法查询下一个需要恢复的应用程序的包名,如第6行所示。
然后,,初始化备份代理类,并调用该类的onCreate()方法,如第7行所示。
再者,查询需要恢复的数据,并调用onRestore()方法执行恢复操作,如8-11行所示。
最后,结束恢复操作并通知应用程序,如18-21行所示。
4.如何使用bmgr工具
此时,我们已经介绍完了备份功能,还原功能及其使用方法。在这个过程中不难发现,bmgr工具起到了至关重要的作用。接下来,我们就将结合bmgr的源代码进一步讲解如何使用这个重要的工具。
bmgr是Android提供的一个shell工具,它使我们能方便地与备份管理器进行交互。但需要注意的是,备份与还原的相关功能只在Android2.2或者更高的版本中才可以使用。
bmgr还提供了一些命令来触发备份和还原操作,因此,我们不需要反复去擦除数据以测试应用程序的备份代理。这里提供的操作主要有强制备份操作,强制还原操作,擦出备份数据以及启用与禁用备份。
①强制备份操作
通常,应用程序必须通过dataChanged()方法来通知备份管理器我们的数据发生了变化,然后备份管理器会在将来的某一个时刻调用实现备份代理的onBackup()方法。此外,还可以使用命令行形式来取代调用dataChanged()方法,其语法结构如下:
adb shell bmgr backup <package-name>
我们先来看看下面的代码片段:
private void doBackup(){ boolean isFull=false; String pkg=nextArg(); ....... try{ mBmgr.dataChanged(pkg); }catch(RemoteException e){ System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); } }
可以看到,备份操作实际上也是执行了一次dataChanged()操作。
当完成以上命令后,备份队列里面就会增加一个备份请求,它会在将来的某一个时刻执行备份,而我们执行一下命令时:
adb shell bmgr run
Android系统的行为将会是怎样的呢?再来看看下面的代码:
private void doRun(){ try{ mBmgr.dataChanged(pkg); }catch(RemoteException e){ System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); } }
上面的代码表明当我们执行run命令后,系统会强制执行备份队列的备份请求,因为它执行了备份管理器的backupNow()方法。
②强制还原操作
和备份操作不一样的是,还原操作会立即执行。目前,Android系统提供了两种类型的还原操作:第一种是使用已有的备份数据去还原整个设备的数据,第二种则是使用某个特定的应用程序将已经备份的数据还原。它们的命令格式如下所示:
Ⅰadb shell bmgr restore <token> Ⅱadb shell bmgr restore <package>
当执行1命令的时候,它会按照token的输入值找到合适的备份数据去还原整个系统。
当执行2命令的时候,与代码中执行备份管理器的requestRestore()方法一样,它会直接调用备份代理类的onRestore()方法。
现在来看看还原操作的源代码:
private void doRestore(){ String arg=nextArg(); .... if(arg.indexOf(',')>=0){ //包名 doRestorePackage(arg); }else{ try{ long token=Long.parseLong(arg,16); doRestoreAll(token); }catch(NumberFormatException e){ ..... } } }
从上面的代码可以看到,当输入的是包名时,将执行一个名叫doRestorePackage()的方法,这个方法主要调用了还原接口的restorePackage()方法,用来还原一个应用程序的备份数据。而当输入的是token的时候,则执行了一个名叫doRestoreAll()方法,这个方法调用了还原接口的restoreAll() 方法,将查询到的所有应用程序的备份数据还原到对应的应用程序上去。
③擦除备份数据
该操作用于单一的应用程序数据,它在开发备份代理的时候非常有用。使用bmgr工具的wipe命令,可以擦除应用程序的数据:
adb shell bmgr wipe <package>
其中<package>是应用程序正式的包名称,该应用程序的数据是希望被擦除的。
Android执行该命令的过程如下所示:
private void doWipe(){ String pkg=nextArg(); .... try{ mBmgr.clearBackupData(pkg); System.out.println("Wiped backup data for"+pkg); }catch(RemoteException e){ System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); } }
如上面代码第5行所示,此命令执行了备份管理器上的clearBackupData()方法,用于擦除对应应用程序备份的数据。
④启动与禁用备份
使用一下命令,可以查看备份管理器是否是可操作的:
adb shell bmgr enabled
也可以用如下命令来直接禁用或启用备份管理器:
adb shell bmgr enable <boolean>
其中boolean或者为true,或者为false,这与设备的设置里禁用或启用备份是一致的。
作为对知识的深挖,有必要介绍一下备份管理器,方法如下表:
方法原型 |
说明 | 使用方法示例 |
public Backup Manager(Context context) | 通过此方法,可通过上下文构造一个备份管理器实例。通过这个实例,我们可以与Android备份系统交互 | BackupManager mBackupManager; mBackupManager=new BackupManager (Context); |
public void dataChanged() | 调用此方法的目的是通知Android备份系统,应用程序希望备份新的修改到它的备份数据上 | mBackupManager.dataChanged(); |
public static void dataChanged(String packageName) | 调用此方法的目的是指明packageName所对应的应用程序为一次备份。 注意:当调用者与参数描述的应用程序包没有运行在相同的uid下时,使用这个方法则需要在引用程序的AndroidManifest.xml文件中声明android.permission.BACKUP权限 |
mBackupManager.dataChanged("com.example.liyuanjing.helloworld") |
public int requestRestore(RestoreObserver observer) | 调用此方法是目的是强制从备份数据集中恢复应用程序的数据。 observer是一个恢复执行的观察者用于通知应用程序恢复的执行状态,包括如下的方法。 1.onUpdate():通知调用者应用程序当前的恢复操作正在执行。 2.restoreStarting():通知调用者应用程序当前的恢复操作已经启动。 3.restoreFinished():通知调用者应用程序当前的恢复操作已经完成。 |
mBackupManager.requestRestore(new RestoreObserver(){ @Override public void restoreFinished(int error){ super.restoreFinished(error); } }); |
现在大家已经学习如何使用backupAgent类和bmgr工具实现备份与恢复。在使用backupAgent类的过程中,我们发现直接使用这个类来实现备份时,需要管理的细节有很多,这导致使用时不太方便。比如,需要管理备份数据的新老状态以及备份数据的关键字等细节问题。在某些特定的场景下,比如在我们打算备份一个完整的文件时,这些文件可以是保存在内部存储器中的文件或者共享文件等,Android SDK就提供了一个帮助类用以简化代码复杂度,它的名字叫BackupAgentHelper。
Android框架提供了两种不同的帮助类,它们是SharedPreferencesBackupHelper和 FileBackupHelper,前者用于备份SharedPreferences文件,后者用于备份来自内部存储器的文件。
值得注意的是,对于每一个需要加到BackAgentHelper中的帮助类,我们都必须在BackupAgentHelper和onCreate()方法中做两件事:实例化所需要的帮助类,调用addHelper()方法将帮助类添加到BackupAgentHelper中。
下面来尝试修改前面的HelloWorld项目。在这个过程中,我们还将使用BackupAgentHelper类来实现对一个文件的备份,具体操作步骤如下:
①修改MainActivity类,在myBackup按钮的单击事件中写一个文件,并将其存储在内部存储器中。修改后的代码如下所示:
public class MainActivity extends Activity { private static final String TAG="MainActivity"; public static final String DATA_FILE_NAME="saved_data"; private Button myBackup; private Button restore; private BackupManager backupManager; public static final Object[] sDataLock=new Object[0]; private File myFile; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); this.myBackup=(Button)findViewById(R.id.myBackup); this.backupManager=new BackupManager(this); this.myFile=new File(getFilesDir(),MainActivity.DATA_FILE_NAME); this.myBackup.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { synchronized (sDataLock){ try { RandomAccessFile file=new RandomAccessFile(myFile,"rw"); file.writeInt(1); } catch (IOException e) { e.printStackTrace(); } backupManager.dataChanged();//加入备份队列准备备份 } } }); this.restore=(Button)findViewById(R.id.restore); this.restore.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { backupManager.requestRestore(new RestoreObserver() { @Override public void restoreFinished(int error) { super.restoreFinished(error); Log.e(TAG,"restoreFinished running"); } }); } }); this.initalFile();//初始化文件 } private void initalFile(){ RandomAccessFile file; synchronized (sDataLock){ boolean exists=this.myFile.exists(); try { file=new RandomAccessFile(this.myFile,"rw"); if(exists){ file.writeInt(1); }else{ file.setLength(0L); file.write(1); } } catch (IOException e) { e.printStackTrace(); } } }}
②新建一个继承自BackupAgentHelper的类来替代原有的BackupAgent子类,用以实现备份及恢复,完成后的代码如下所示:
public class MyBackupAgentHelper extends android.app.backup.BackupAgentHelper { private static final String TAG="BackupAgentHelper"; public static final String FILE_HELPER_KEY="myback"; @Override public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) throws IOException { //这里我们无需要做任何任何事情,只需要把它交给框架即可 synchronized (MainActivity.sDataLock){ super.onBackup(oldState, data, newState); } Log.e(TAG,"onBackup is running"); } @Override public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) throws IOException { //这里我们无需要做任何任何事情,只需要把它交给框架即可 synchronized (MainActivity.sDataLock){ super.onRestore(data, appVersionCode, newState); } Log.e(TAG,"onRestore is running"); } @Override public void onCreate() { //这里我们首先实例化一个FileBackupHelper实例 //并使用它作为参数之一调用addHelper()方法完成初始化 FileBackupHelper file_helper=new FileBackupHelper(this,MainActivity.DATA_FILE_NAME); addHelper(FILE_HELPER_KEY,file_helper); } }
③修改AndroidManifest.xml文件中的android:backupAgent,将MyBackupAgentHelper作为其属性
④编译并运行应用程序。此时,当单击应用程序的“Backup”按钮并运行adb shell bmgr run命令之后,Android就开始备份了。但当单击应用程序的restore按钮时,Android将恢复这个文件。
到这里,我们就介绍完Android:backupAgent属性的作用及其用法了。