作者:TechMerger
转载地址:https://juejin.cn/post/6956237862815662088
针对Backup功能的前作足足三万字,立足点比较大,本篇将针对实战环节单独解读。
手机等智能设备是现代生活中的重要角色,我们会在这些智能设备上做登录账户,设置偏好,拍摄照片,保存联系人等日常操作。这些数据耗费了我们很多时间和精力,对我们而言极为重要。
如果我们的设备换代了或者重新安装了某个应用,之前使用的数据如果能自动保留,那将是非常出色的用户体验。而保留数据的第一步则在于Backup环节。
一、基本认识
备份的数据可以笼统地划分为三类:登录账号相关的身份数据、系统设置相关的偏好以及各App的数据。本次讨论的对象在于App数据。
而App数据基本涵盖在如下类型。
类型 | 路径 | 取得对应文件的API |
---|---|---|
data |
/data/data/com.xxx/ | getDataDir()/getDir() |
files |
/data/data/com.xxx/files/ | getFilesDir() |
databases |
/data/data/com.xxx/databases/ | getDatabasePath() |
sharedpreferences |
/data/data/com.xxx/sp/ | getSharedPreferences() |
Backup操作从最外层的data目录开始,按照文件单位逐个读取逐个备份。目录内的文件一般按照文件名的顺序进行备份,但这个顺序无法保证,取决于File#list()
API的结果。
Android 6.0之前Backup功能只有键值对备份
(Key-value Backup)这一种模式,而且默认是关闭的。想要打开键值对备份功能得将allowBackup属性设置为true,并指定BackupAgent实现。
6.0之后allowBackup属性默认为true,但是新引入的自动备份
(Auto Backup)。自动备份模式执行全体备份和恢复,便捷够用更推荐。
两个模式在备份的频次、文件的存放位置、恢复的执行时机等细节都很不一样,下面将针对两种模式展开实战演示。
备份模式 | 键值对备份 | 自动备份 |
---|---|---|
支持版本 | Android 2.2 | Android 6.0 |
开关办法 | 默认关闭,需手动开启allowBackup并指定BackupAgent | 默认开启,关闭需要将allowBackup置为false |
备份定制 | BackupAgent里指定备份和恢复的文件 | 可以通过XML配置备份和不备份的文件列表,也可以通过BackupAgent改写备份恢复的逻辑,定制性高 |
备份时机 | 需要App调用API手动发起备份 | 自动进行,大约每天一次 |
备份的托管位置 | Android Backup Service/Google服务器 | Google Drive云盘 |
备份限制 | 上限只有5M | 上限有25M |
恢复时机 | APK安装的时候自动恢复,也可以调用API手动发起恢复 | APK安装的时候自动恢复 |
原理细节 | 回调到BackupAgent的onBackup()和onRestore() | 回调到BackupAgent的onFullBackup()和onRestoreFile() |
二、实战
Ⅰ. 准备工作
ⅰ. 思考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地址:github.com/ellisonchan…
Ⅱ. 选择备份模式
如果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