针对Backup功能的前作足足三万字,立足点比较大,本篇专门针对功能的实战环节进行解读。
手机等智能设备是现代生活中的重要角色,我们会在这些智能设备上做登录账户,设置偏好,拍摄照片,保存联系人等日常操作。这些数据耗费了我们很多时间和精力,对我们而言极为重要。
如果我们的设备换代了或者重新安装了某个应用,之前使用的数据如果能自动保留,那将是非常出色的用户体验。而保留数据的第一步则在于Backup环节。
一、基本认识
备份的数据可以笼统地划分为三类:登录账号相关的身份数据、系统设置相关的偏好以及各App的数据。本次讨论的对象在于App数据。
而App数据基本涵盖在如下类型。
Backup操作从最外层的data目录开始,按照文件单位逐个读取逐个备份。目录内的文件一般按照文件名的顺序进行备份,但这个顺序无法保证,取决于File#list() API的结果。
Android 6.0之前Backup功能只有键值对备份(Key-value Backup)这一种模式,而且默认是关闭的。想要打开键值对备份功能得将allowBackup属性设置为true,并指定BackupAgent实现。
6.0之后allowBackup属性默认为true,但是新引入的自动备份(Auto Backup)。自动备份模式执行全体备份和恢复,便捷够用更推荐。
两个模式在备份的频次、文件的存放位置、恢复的执行时机等细节都很不一样,下面将针对两种模式展开实战演示。
二、实战
Ⅰ. 准备工作
ⅰ. 思考Backup的需求
在定制所需的Backup功能前,先了解清楚自己的Backup需求,比如尝试问自己如下几个问题。
备份的数据Size会很大吗?超过5M甚至25M吗?
应用的数据全部都需要备份吗?
如果数据很大,需要对应用的部分数据做出取舍,哪些数据可以舍弃?
如果恢复的数据的版本不同,能直接恢复吗?该怎么定制?
定制后的数据能保证继续读写吗?
ⅱ. 准备测试Demo
我们先做个涉及到Data、File、DB以及SP这四种类型数据的App,后面针对这个Demo进行各种Backup功能的定制演示。
Demo通过Jetpack Hilt完成依赖注入,写入数据的逻辑简述如下:
首次打开的时候尚未产生数据,点击Init Button后会将预设的电影海报保存到Data目录,电影Bean实例序列化到File目录,同时通过Jetpack Room将该实例保存到DB。如果三个操作成功执行将初始化成功的Flag标记到SP文件
再次打开的时候依据SP的Flag将会直接读取这四种类型的数据反映到UI上
Demo地址:https://github.com/ellisonchan/BackupRestoreApp
Ⅱ. 选择备份模式
如果Backup需求不复杂,那优先选择自动备份
模式。因为这个模式提供的空间更大、定制也更灵活。是Google首推的Backup模式。
如果应用数据Size很小而且愿意手动实现DB文件的备份恢复逻辑的话,可以采用键值对备份
模式。
Ⅲ. 自动备份
鉴于键值对备份的诸多不足,Google在6.0推出的自动备份模式带来了很多改善。
自动执行无需手动发起
更大的备份空间(由原来的5M变成了25M)
更多类型文件的支持(在File和SP文件以外还支持了Data和DB文件)
更简单的备份规则(通过XML即可快速指定备份对象)
更安全的备份条件(在规则中指定flag可限定备份执行的条件)
ⅰ. 基本定制
想要支持自动备份模式的话,什么代码也不用写,因为6.0开始自动备份模式默认打开。但我还是推荐开发者明确地打开allowBackup
属性,这表示你确实意识到Backup功能并决定支持它。
<manifest ... > <application android:allowBackup="true" ... /> </manifest>
开启之后同样使用adb命令模拟备份恢复的过程,通过截图可以看到所有数据都被完整恢复了
// Backup >adb backup -f auto-backup.ab -apk com.ellison.backupdemo // Clear data >adb shell pm clear com.ellison.backupdemo // Restore >adb restore auto-backup.ab
ⅱ. 简单的备份规则
通过fullBackupContent
属性可以指向包含备份规则的 XML 文件。我们可以在规则里决定了备份哪些文件,无视哪些文件。
比如只需要备份放在Data的海报图片和SP,不需要File和DB文件
<manifest ... > <application android:allowBackup="true" android:fullBackupContent="@xml/my_backup_rules" ... /> </manifest>
<!-- my_backup_rules.xml --> <full-backup-content> <!-- include指定参与备份的文件 --> <!-- domain指定root代表这个的规则适用于data目录 --> <include domain="root" path="Post.jpg" /> <include domain="sharedpref" path="." /> <!-- exclude指定不参与备份的文件 --> <!-- path里指定.代表该目录下所有文件都适用这个规则,免去逐个指定各个文件 --> <exclude domain="file" path="." /> <exclude domain="database" path="." /> </full-backup-content>
运行下备份和恢复的命令可以看到如下File和DB确实没有备份成功。
ⅲ.补充规则所需的条件
当某些隐私程度极高的数据,不放心被备份在网络里,但如果数据被加密的话可以考虑。面对这种有条件的备份,Google提供了requireFlags属性来解决。
通过在XML规则里给属性指定如下value可以补充备份操作的额外条件。
clientSideEncryption:只在手机设置了密码等密钥的情况下执行备份
deviceToDeviceTransfer:只在D2D的设备间备份的情况下执行备份
在上述规则上增加一个条件:只在设备设置密码的情况下备份海报图片。
<!-- my_backup_rules.xml --> <full-backup-content> <include domain="root" path="Post.jpg" requireFlags="clientSideEncryption" /> ... </full-backup-content>
如果设备未设置密码,运行下备份和恢复的命令可以看到图片确实也被没有备份。
可是设置了密码,而且打开了Backup功能,无论使用backup命令还是bmgr工具都没能将图片备份。clientSideEncryption的真正条件看来没能被满足,后期继续研究。
如果您已将开发设备升级到 Android 9,则需要在升级后停用数据备份功能,然后再重新启用。这是因为只有当在“设置”或“设置向导”中通知用户后,Android 才会使用客户端密钥加密备份。
ⅳ.定制备份的流程
如果XML定制备份规则的方案还不能满足需求的话,可以像键值对备份模式一样指定BackupAgent,来更灵活地控制备份流程。
可是指定了BackupAgent的话默认会变成键值对备份模式。我们如果仍想要更优的自动备份模式怎么办?Google考虑到了这点,只需再打开fullBackupOnly这个属性。(像极了我们改Bug时候不断引入新Flag的操作。。。)
<manifest ... > ... <application android:allowBackup="true" android:backupAgent=".MyBackupAgent" android:fullBackupOnly="true" ... /> </manifest>
class MyBackupAgent: BackupAgentHelper() { override fun onCreate() { Log.d(Constants.TAG_BACKUP, "onCreate()") super.onCreate() } override fun onDestroy() { Log.d(Constants.TAG_BACKUP, "onDestroy()") super.onDestroy() } override fun onFullBackup(data: FullBackupDataOutput?) { Log.d(Constants.TAG_BACKUP, "onFullBackup()") super.onFullBackup(data) } override fun onRestoreFile(... ) { Log.d(Constants.TAG_BACKUP, "onRestoreFile() destination:$destination type:$type mode:$mode mtime:$mtime") super.onRestoreFile(data, size, destination, type, mode, mtime) } // Callback when restore finished. override fun onRestoreFinished() { Log.d(Constants.TAG_BACKUP, "onRestoreFinished()") super.onRestoreFinished() } }
这样子便可以在定制Backup流程的依然采用自动备份模式,两全其美。
>adb backup -f auto-backup.ab -apk com.ellison.backupdemo
>adb logcat -s BackupManagerService -s BackupRestoreAgent BackupRestoreAgent: MyBackupAgent() BackupRestoreAgent: onCreate() BackupManagerService: agentConnected pkg=com.ellison.backupdemo agent=android.os.BinderProxy@3c0bc60 BackupManagerService: got agent android.app.IBackupAgent$Stub$Proxy@4b5a519 BackupManagerService: Calling doFullBackup() on com.ellison.backupdemo BackupRestoreAgent: onFullBackup() ★ BackupManagerService: Adb backup processing complete. BackupRestoreAgent: onDestroy() AndroidRuntime: Shutting down VM BackupManagerService: Full backup pass complete. ★
注意:
6.0之前的系统尚未支持自动备份模式,allowBackup
打开也只支持键值对模式。而fullBackupOnly
属性的补充设置也会被系统无视。