全面复盘Android开发者容易忽视的Backup功能 _ 创作者训练营第二期

// 发起恢复请求的命令很简单
adb restore .ab

接下来输入密码开始恢复,同样的会有Toast提示恢复的进度。 在这里插入图片描述

ⅱ.bmgr工具

adb backup命令提供的功能不够强大,官方推荐bmgr工具。它将备份和恢复的步骤分得更细,便于我们理清各个环节,更好的协助我们测试备份和恢复的逻辑。

bmgr工具没有UI,完全通过命令在后台默默运行。

首先需要启用它。 注意:要确保设置里的Backup功能没有被关闭,Settings > Backup & Restore。

adb shell bmgr enabled
Backup Manager currently enabled

接着,查看ROM里支持的文件传输服务,*号表示当前选择的服务。

adb shell bmgr list transports
com.android.localtransport/.LocalTransport
com.google.android.gms/.backup.migrate.service.D2dTransport

  • com.google.android.gms/.backup.BackupTransportService

GMS的传输服务要求设备联网和科学上网,为方面测试我们切换服务为本地传输

adb shell bmgr transport com.android.localtransport/.LocalTransport
Selected transport com.android.localtransport/.LocalTransport (formerly com.google.android.gms/.backup.BackupTransportService)

查看传输服务的更改是否生效。

adb shell bmgr list transports

  • com.android.localtransport/.LocalTransport
    com.google.android.gms/.backup.migrate.service.D2dTransport
    com.google.android.gms/.backup.BackupTransportService

针对某个App发起备份。

adb shell bmgr backupnow

在另一个终端捕捉备份的执行日志,有可能会提示没有设置锁屏密码

Backup : [CryptoEnableCheck] Should not encrypt backups: device has no lock screen.

设置密码后再次发起备份,可以看到成功备份了。

adb shell bmgr backupnow
Package xxx with result: Success
Backup finished with result: Success

日志终端也显示回调了App指定的BackupAgent

AndroidRuntime: Calling main entry com.android.commands.bmgr.Bmgr
PFTBT : backupmanager pftbt token=4081832e
BackupManagerService: awaiting agent for ApplicationInfo{30f779b xxx}
BackupRestoreAgent: MyBackupAgent()
BackupRestoreAgent: onCreate()
BackupManagerService: agentConnected pkg=xxx agent=android.os.BinderProxy@5f88b66
BackupManagerService: got agent android.app.IBackupAgent S t u b Stub StubProxy@c309ea7
BackupRestoreAgent: onBackup()
BackupRestoreAgent: onDestroy()

bmgr工具在手动恢复的时候需要Token信息,通过dumpsys backup获取对应的Token。Token来自于AncestralCurrent两个标签的组合,比如本次的Token为01。

adb shell dumpsys backup
Backup Manager is enabled / setup complete / not pending init
Auto-restore is enabled
No backups running
Last backup pass started: 1619317275335 (now = 1619319671619)
next scheduled: 1619332172012

Ancestral: 0 ★
Current: 1 ★

清空App数据。

adb shell pm clear

手动恢复数据,从命令和日志两个终端都能看到数据被正确恢复了。

adb shell bmgr restore 01
Scheduling restore: Local disk image
restoreStarting: 1 packages
onUpdate: 0 = com.example.alldemo
restoreFinished: 0
done

BackupRestoreAgent: MyBackupAgent()
BackupRestoreAgent: onCreate()
BackupManagerService: agentConnected pkg=com.example.alldemo agent=android.os.BinderProxy@a480a0c
BackupManagerService: got agent android.app.IBackupAgent S t u b Stub StubProxy@5041f55
BackupManagerService: initiateOneRestore packageName=xxx
BackupRestoreAgent: onRestore()
BackupManagerService: restoreFinished packageName=xxx
BackupRestoreAgent: onRestoreFinished()
BackupManagerService: Restore complete, killing host process of xxx ★
BackupRestoreAgent: onDestroy()
BackupManagerService: No more packages; finishing restore
BackupManagerService: Restore complete.

当然将App卸载后通过市场或手动安装可以自动地恢复数据,这个动作由系统在Apk安装的时候自动完成。

Transport服务的选择要小心。如果选了GMS Transport的话,要注意GMS场景的网络问题,不然备份会失败。

更加详细的bmgr使用方法可参考如下文档。

developer.android.google.cn/studio/comm…

3.1.3 Google发起

Google将会按照每日一次的频次对支持自动备份模式的App发起备份操作。

恢复的话则是在设备第一次开机登录Google账号后。Google会将数据从服务器下载通过BackupManager向各个备份过的App发起恢复操作。尚未安装的App则在后期Apk安装完成之后由Google自行发起恢复。

3.2 Backup/Restore的调试

logcat指定BackupManagerService的Tag,可以监听到Backup和Restore的日志,辅助我们把握操作的进度和报错的原因。

adb logcat -s BackupManagerService

比如针对Google Photos App进行adb备份和恢复操作的时候,将会输出如下日志。

  • Backup

adb logcat -s BackupManagerService
BackupManagerService: Requesting backup: apks=true obb=false shared=false all=false system=true includekeyvalue=false pkgs=[Ljava.lang.String;@190020e
BackupManagerService: Beginning adb backup…
BackupManagerService: Starting backup confirmation UI, token=1441721864
BackupManagerService: Waiting for backup completion…
BackupManagerService: acknowledgeAdbBackupOrRestore : token=1441721864 allow=true
BackupManagerService: — Performing adb backup —
BackupManagerService: Package com.google.android.apps.photos is key-value.
BackupManagerService: Adb backup processing complete.
BackupManagerService: Full backup pass complete.

  • Restore

adb logcat -s BackupManagerService
BackupManagerService: Beginning restore…
BackupManagerService: Starting restore confirmation UI, token=1694423050
BackupManagerService: Waiting for restore completion…
BackupManagerService: acknowledgeAdbBackupOrRestore : token=1694423050 allow=true
BackupManagerService: — Performing full-dataset restore —
BackupManagerService: adb restore processing complete.
BackupManagerService: Full restore pass complete.

一般来说BackupManagerService提供的日志情报足够了,但在调试Transport,使用bmgr工具等场景的时候,还可以使用这些Tag获得更详细的日志:Backup,BackupManager,PFTBT,GmsBackupTransport,PerformBackupTask和RestoreSession等。

adb logcat -s AndroidRuntime -s Backup -s BackupManager -s BackupManagerService -s PFTBT -s GmsBackupTransport -s -s PerformBackupTask -s RestoreSession

3.3 Backup文件的解密

Backup文件的后缀名为.ab,估计是android backup的缩写。我们用Text打开上面备份的Google Photos文件,可以看到如下信息。

ANDROID BACKUP
5
1
AES-256
C356E772D89C31C0FCAE6BF16BEC2FF90F0503BCD12111B380FF6054B823D80963EEDC661D92DB908788B48499A80B62731C1A9822C8BF5CD8D67AE85FF45CD9

整个文件内容包含头和内容,其中头的信息非常重要,关乎到备份的策略和解密的方式。

  • Backup功能的版本号,比如上面的5,定义在源码的UserBackupManagerService文件中
  • Backup备份文件是否压缩,比如上面的1意味着经过了压缩
  • Backup加密方式,比如上面采用了AES-256加密算法,如果未输入密码备份的话,此处会显示none

未输入密码的ab文件。

ANDROID BACKUP
5
1
none
xレb

我们可以使用abe.jar来解密备份的文件,如果使用了加密算法的话,还需要Java Cryptography Extension jar包的帮助。

这里简单演示下没有加密的备份文件的破解过程。

// 输入如下命令
java -jar abe.jar unpack backupFileName-nopwd.ab backupFileName-nopwd.tar

未输出任何Exception则表示解密成功,并会生成指定的tar包。解压出来之后是包括DB、SP在内的原始数据。 在这里插入图片描述

abe.jar全名为android-backup-extractor,是采用Java语言编写的转为解密Android备份文件的工具,非常好用。

abe.jar下载地址

除了这个工具,貌似DD命令也可以破解,笔者没有试过,感兴趣的可以参考如下文章进行更深入的尝试。

浅谈安卓系统备份文件ab格式解析

4. 实战

铺垫了关于Backup功能的大量知识,就是想让完整地认识和理解这个功能。接下来进入最实用的实战环节。

4.1 准备工作

4.1.1 思考Backup的需求

在定制所需的Backup功能前,先了解清楚自己的Backup需求,比如尝试问自己如下几个问题。

  • 备份的数据Size会很大吗?超过5M甚至25M吗?
  • 应用的数据全部都需要备份吗?
  • 如果数据很大,需要对应用的部分数据做出取舍,哪些数据可以舍弃?
  • 如果恢复的数据的版本不同,能直接恢复吗?该怎么定制?
  • 定制后的数据能保证继续读写吗?
4.1.2 准备测试Demo

我们先做个涉及到DataFileDB以及SP这四种类型数据的App,后面针对这个Demo进行各种Backup功能的定制演示。

Demo通过Jetpack Hilt完成依赖注入,写入数据的逻辑简述如下:

  • 首次打开的时候尚未产生数据,点击Init Button后会将预设的电影海报保存到Data目录,电影Bean实例序列化到File目录,同时通过Jetpack Room将该实例保存到DB。如果三个操作成功执行将初始化成功的Flag标记到SP文件
  • 再次打开的时候依据SP的Flag将会直接读取这四种类型的数据反映到UI上

Demo地址:github.com/ellisonchan…

在这里插入图片描述 在这里插入图片描述

4.2 选择备份模式

如果Backup需求不复杂,那优先选择自动备份模式。因为这个模式提供的空间更大、定制也更灵活。是Google首推的Backup模式。

如果应用数据Size很小而且愿意手动实现DB文件的备份恢复逻辑的话,可以采用键值对备份模式。

4.3 自动备份

鉴于键值对备份的诸多不足,Google在6.0推出的自动备份模式带来了很多改善。

  • 自动执行无需手动发起
  • 更大的备份空间(由原来的5M变成了25M)
  • 更多类型文件的支持(在File和SP文件以外还支持了Data和DB文件)
  • 更简单的备份规则(通过XML即可快速指定备份对象)
  • 更安全的备份条件(在规则中指定flag可限定备份执行的条件)
ⅰ. 基本定制

想要支持自动备份模式的话,什么代码也不用写,因为6.0开始自动备份模式默认打开。但我还是推荐开发者明确地打开allowBackup属性,这表示你确实意识到Backup功能并决定支持它

<manifest … >
<application android:allowBackup=“true” … />

开启之后同样使用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” … />

运行下备份和恢复的命令可以看到如下File和DB确实没有备份成功。 在这里插入图片描述

ⅲ.补充规则所需的条件

当某些隐私程度极高的数据,不放心被备份在网络里,但如果数据被加密的话可以考虑。面对这种有条件的备份,Google提供了requireFlags 属性来解决。

通过在XML规则里给属性指定如下value可以补充备份操作的额外条件。

  • clientSideEncryption:只在手机设置了密码等密钥的情况下执行备份
  • deviceToDeviceTransfer:只在D2D的设备间备份的情况下执行备份

在上述规则上增加一个条件:只在设备设置密码的情况下备份海报图片。

...

如果设备未设置密码,运行下备份和恢复的命令可以看到图片确实也被没有备份。 在这里插入图片描述 可是设置了密码,而且打开了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” … />

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: d e s t i n a t i o n t y p e : destination type: destinationtype:type mode: m o d e m t i m e : mode mtime: modemtime: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 S t u b Stub StubProxy@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属性的补充设置也会被系统无视。

ⅴ.进阶定制之限制备份来源

与中国市场上大都售卖无锁版设备不同,海外售卖的不少设备是绑定运营商的。而不同运营商上即便同一个应用,它们预设的数据可能都不同。这时候我们可能需要对备份数据的来源做出限制。

简言之A设备上面备份数据限制恢复到B设备。 在这里插入图片描述

如何实现

因为自动备份模式下不会将数据的appVersionCode传回来,所以判断应用版本的办法行不通。而且有的时候应用版本是一致的,只是运营商不一致。

所以需要我们自己实现,大家可以自行思考。先说我之前想到的几种方案。

  1. 备份的时候将设备的名称埋入SP文件,恢复的时候检查SP文件里的值
  2. 备份的时候将设备的名称埋入新的File文件,恢复的时候检查File文件的值

这俩方案的缺陷: 方案1的缺点在于备份的逻辑会在原有的文件里增加值,会影响现有的逻辑。

方案2增加了新文件,避免对现有的逻辑造成影响,对方案1有所改善。但它和方案1都存在一个潜在的问题。

问题在于无法保证这个新文件首先被恢复到,也就无保证在恢复执行的一开始就知道本次恢复是否需要。

假使恢复进行到了一半,轮到标记新文件的时候才发现本次恢复需要丢弃,那么将会导致数据错乱。因为系统没有提供Roll back已恢复数据的API,如果我们自己也没做好保存和回退旧的文件处理的话,最后必然发生部分文件已恢复部分没恢复的不一致问题。

要理解这个问题就要搞清楚恢复操作针对文件的执行顺序。

自动备份模式在恢复的时候会逐个调用onRestoreFile(),将各个目录下备份的文件回调过来。目录之间的顺序和备份时候的顺序一致,如下备份的代码可以看出来:从根目录的Data开始,接着File目录开始,然后DB和SP文件。

public abstract class BackupAgent extends ContextWrapper {

public void onFullBackup(FullBackupDataOutput data) throws IOException {

// Root dir first.
applyXmlFiltersAndDoFullBackupForDomain(
packageName, FullBackup.ROOT_TREE_TOKEN, manifestIncludeMap,
manifestExcludeSet, traversalExcludeSet, data);
// Data dir next.
traversalExcludeSet.remove(filesDir);
// Database directory.
traversalExcludeSet.remove(databaseDir);
// SharedPrefs.
traversalExcludeSet.remove(sharedPrefsDir);
}
}

文件内的顺序则通过File#list()获取,而这个API是无法保证得到的文件列表都按照abcd的字母排序。所以在File目录下放标记文件不能保证它首先被恢复到。即便放一个a开头的标记文件也不能完全保证。

★推荐方案★

一般的App鲜少在根目录存放数据,而根目录最先被恢复到。所以我推荐的方案是这样的。

备份的时候将设备的名称埋入根目录的特定文件,恢复的时候检查该File文件,在恢复的初期就决定本次恢复是否需要。为了不影响恢复之后的正常使用,最后还要删除这个标记文件。

废话不多说,看下代码。

  • Backup里放入标记文件。

class MyBackupAgent : BackupAgentHelper() {

override fun onFullBackup(data: FullBackupDataOutput?) {
// ★ 在备份执行前先将标记文件写入Data目录
// Make backup source file before full backup invoke.
writeBackupSourceToFile()
super.onFullBackup(data)
}

private fun writeBackupSourceToFile() {
val sourceFile = File(dataDir.absolutePath + File.separator

  • Constants.BACKUP_SOURCE_FILE_PREFIX + Build.MODEL)
    if (!sourceFile.exists()) {
    sourceFile.createNewFile()
    }
    }

    }
  • Restore检查标记文件。

class MyBackupAgent : BackupAgentHelper() {
private var needSkipRestore = false

override fun onRestoreFile(
data: ParcelFileDescriptor?,
size: Long,
destination: File?,
type: Int,
mode: Long,
mtime: Long
) {
if (!needSkipRestore) {
val sourceDevice = readBackupSourceFromFile(destination)
// ★ 备份源设备名和当前名不一致的时候标记需要跳过
// Mark need skip restore if source got and not match current device.
if (!TextUtils.isEmpty(sourceDevice) && !sourceDevice.equals(Build.MODEL)) {
needSkipRestore = true
}
}

if (!needSkipRestore) {
// Invoke restore if skip flag set.
super.onRestoreFile(data, size, destination, type, mode, mtime)
} else {
// ★ 跳过备份但一定要消费stream防止恢复的进程阻塞
// Consume data to keep restore stream go.
consumeData(data!!, size, type, mode, mtime, null)
}
}

private fun readBackupSourceFromFile(file: File?): String {
if (file == null) return “”
var decodeDeviceSource = “”

// Got data file with backup source mark.
if (file.name.startsWith(Constants.BACKUP_SOURCE_FILE_PREFIX)) {
decodeDeviceSource = file.name.replace(Constants.BACKUP_SOURCE_FILE_PREFIX, “”)
}
return decodeDeviceSource
}

@Throws(IOException::class)
fun consumeData(data: ParcelFileDescriptor,
size: Long, type: Int, mode: Long, mtime: Long, outFile: File?) {

}
}

  • 无论是Backup还是Restore都要将标记文件移除。

class MyBackupAgent : BackupAgentHelper() {

override fun onDestroy() {
super.onDestroy()
// 移除标记文件
// Ensure temp source file is removed after backup or restore finished.
ensureBackupSourceFileRemoved()
}

private fun ensureBackupSourceFileRemoved() {
val sourceFile = File(dataDir.absolutePath + File.separator

  • Constants.BACKUP_SOURCE_FILE_PREFIX + Build.MODEL)
    if (sourceFile.exists()) {
    val result = sourceFile.delete()
    }
    }
    }

接下里验证代码能否拦截不同设备的备份文件。先在小米手机里备份文件,然后到Pixel模拟器里恢复这个数据。

  • 小米里备份

adb -s c7a1a50c7d27 backup -f auto-backup-cus-xiaomi.ab -apk com.ellison.backupdemo

adb -s c7a1a50c7d27 logcat -s BackupManagerService -s BackupRestoreAgent
BackupManagerService: — Performing full backup for package com.ellison.backupdemo —
BackupRestoreAgent: onCreate()
BackupManagerService: agentConnected pkg=com.ellison.backupdemo agent=android.os.BinderProxy@5e68506
BackupManagerService: got agent android.app.IBackupAgent S t u b Stub StubProxy@852a7c7
BackupManagerService: Calling doFullBackup() on com.ellison.backupdemo
BackupRestoreAgent: onFullBackup()
// ★标记文件里写入了小米的设备名称并备份了
BackupRestoreAgent: writeBackupSourceToFile() sourceFile:/data/user/0/com.ellison.backupdemo/backup-source-Redmi 6A create:true ★
BackupRestoreAgent: onDestroy()
BackupManagerService: Adb backup processing complete.
BackupRestoreAgent: ensureBackupSourceFileRemoved() sourceFile:/data/user/0/com.ellison.backupdemo/backup-source-Redmi 6A delete:true ★
BackupManagerService: Full backup pass complete.

  • Pixel里恢复,可以看到Pixel的日志里显示跳过了恢复

adb -s emulator-5554 restore auto-backup-cus-xiaomi.ab

adb -s emulator-5554 logcat -s BackupManagerService -s BackupRestoreAgent
BackupManagerService: — Performing full-dataset restore —

BackupRestoreAgent: onRestoreFile() destination:/data/data/com.ellison.backupdemo/backup-source-Redmi 6A type:1 mode:384 mtime:1619355877 currentDevice:sdk_gphone_x86_arm needSkipRestore:false
BackupRestoreAgent: readBackupSourceFromFile() file:/data/data/com.ellison.backupdemo/backup-source-Redmi 6A
BackupRestoreAgent: readBackupSourceFromFile() source:Redmi 6A
BackupRestoreAgent: onRestoreFile() sourceDevice:Redmi 6A
// ★从备份数据里读取到了小米的设备名,不同于Pixel模拟器的名称,设定了跳过恢复的flag
BackupRestoreAgent: onRestoreFile() destination:/data/data/com.ellison.backupdemo/Post.jpg type:1 mode:384 mtime:1619355781 currentDevice:sdk_gphone_x86_arm needSkipRestore:true
BackupRestoreAgent: onRestoreFile() skip restore and consume ★

BackupRestoreAgent: onRestoreFinished()
BackupManagerService: [UserID:0] adb restore processing complete.
BackupRestoreAgent: onDestroy()
BackupManagerService: Full restore pass complete.

Pixel模拟器上重新打开App之后确实没有任何数据。 在这里插入图片描述

当然如果App确实有在根目录下存放数据,那么建议你仍采用这个方案。

只不过需要给这个特定文件加一个a的前缀,以保证它大多数情况下会被先恢复到。当然为了防止极低的概率下它没有首先被恢复,开发者还需自行加上一个Data目录下文件的暂存和回退处理,以防万一。

更高的定制需求

如果发现备份的设备名称不一致的时候,客户的需求并不是丢弃恢复,而是让我们将运营商之间的diff merge进来呢?

这里提供一个思路。在上述方案的基础之上改下就行了。

比如恢复的一开始通过标记的文件发现备份的不一致,丢弃恢复的同时将待恢复的文件都改个别名暂存到本地。应用再次打开的时候读取暂存的数据和当前数据做对比,然后将diff merge进来

ⅵ.BackupAgent和配置规则的混用

BackupAgent和XML配置并不冲突,在backup逻辑里还可以获取配置的设备条件。比如在onFullBackup()里可以利用FullBackupDataOutput的getTransportFlags()来取得相应的Flag来执行相应的逻辑。

  • FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED 对应着设备加密条件
  • FLAG_DEVICE_TO_DEVICE_TRANSFER 对应D2D备份场景条件

class MyBackupAgent: BackupAgentHelper() {

override fun onFullBackup(data: FullBackupDataOutput?) {
Log.d(Constants.TAG_BACKUP, “onFullBackup()”)
super.onFullBackup(data)

if (data != null) {
if ((data.transportFlags and FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED) != 0) {
Log.d(Constants.TAG_BACKUP, “onFullBackup() CLIENT ENCRYPTION NEED”)
}
}
}
}

4.4 键值对备份

键值对备份支持的空间小,而且针对File类型的Backup实现非线程安全,同时需要自行考虑DB这种大空间文件的备份处理,并不推荐使用。

但本着学习的目的还是要了解一下。

ⅰ. 基本定制

使用这个模式需额外指定BackupAgent并实现其细节。

<manifest … >
<application android:allowBackup=“true”
android:backupAgent=“.MyBackupAgent” … >



BackupAgent的实现在于告诉BMS每个类型的文件采用什么Key备份和恢复。可以选择高度定制的复杂办法去实现,当然SDK也提供了简单办法。

  • 复杂办法:直接扩展自BackupAgent抽象类,需要自行实现onBackup()onRestore的细节。包括读取各类型文件并调用对应的Helper实现写入数据到备份文件中以及考虑旧的备份数据的迁移等处理。需要考虑很多细节,代码量很大
  • 简单办法:扩展自系统封装好的BackupAgentHelper类并告知各类型文件对应的KEY和Helper实现即可,高效而简单,但没有提供大容量文件比如DB的备份实现

以扩展BackupAgentHelper的简单办法为例,演示下键值对备份的实现。

  • SP文件的话SDK提供了特定的SharedPreferencesBackupHelper实现
  • File文件对应的Helper实现为FileBackupHelper,只限于file目录的数据
  • 其他类型文件比如Data和DB是没有预设Helper实现的,需要自行实现BackupHelper

// MyBackupAgent.kt
class MyBackupAgent: BackupAgentHelper() {
override fun onCreate() {

// Init helper for data, file, db and sp files.
// Data和DB文件使用FileBackupHelper是无法备份的,此处单纯为了验证下
FileBackupHelper(this, Constants.DATA_NAME).also { addHelper(Constants.BACKUP_KEY_DATA, it) }
FileBackupHelper(this, Constants.DB_NAME).also { addHelper(Constants.BACKUP_KEY_DB, it) }
// File和SP各自使用对应的Helper是可以备份的
FileBackupHelper(this, Constants.FILE_NAME).also { addHelper(Constants.BACKUP_KEY_FILE, it) }
SharedPreferencesBackupHelper(this, Constants.SP_NAME).also { addHelper(Constants.BACKUP_KEY_SP, it) }
}

}

先用bmgr工具执行Backup,然后清除Demo的数据再执行Restore。从日志可以看出来键值对备份和恢复成功进行了。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

结尾

好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划,可以来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。

这里放上一部分我工作以来以及参与过的大大小小的面试收集总结出来的一套进阶学习的视频及面试专题资料包,主要还是希望大家在如今大环境不好的情况下面试能够顺利一点,希望可以帮助到大家~

(img-qw4RDKhE-1712061517616)]
[外链图片转存中…(img-E0AoGoqw-1712061517617)]
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-6u3BS0TE-1712061517617)]

结尾

好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划,可以来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。

这里放上一部分我工作以来以及参与过的大大小小的面试收集总结出来的一套进阶学习的视频及面试专题资料包,主要还是希望大家在如今大环境不好的情况下面试能够顺利一点,希望可以帮助到大家~

[外链图片转存中…(img-MvWyFd4e-1712061517617)]

[外链图片转存中…(img-GyZHgBr6-1712061517617)]

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值