前言
手机等智能设施是古代生存中的重要角色,咱们会在这些智能设施上做登录账户,设置偏好,拍摄照片,保留联系人等日常操作。这些数据消耗了咱们很多工夫和精力,对咱们而言极为重要。
如果咱们的设施换代了或者重新安装了某个利用,之前应用的数据如果能主动保留,那将是十分杰出的用户体验。而保留数据的第一步则在于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/ellisoncha...
抉择备份模式
如果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属性的补充设置也会被零碎忽视。
进阶定制之限度备份起源
与中国市场上大都售卖无锁版设施不同,海内售卖的不少设施是绑定运营商的。而不同运营商上即使同一个利用,它们预设的数据可能都不同。这时候咱们可能须要对备份数据的起源做出限度。
简言之A设施下面备份数据限度复原到B设施。
如何实现?
因为主动备份模式下不会将数据的appVersionCode传回来,所以判断利用版本的方法行不通。而且有的时候利用版本是统一的,只是运营商不统一。
所以须要咱们本人实现,大家能够自行思考。先说我之前想到的几种计划。
- 备份的时候将设施的名称埋入SP文件,复原的时候查看SP文件里的值
- 备份的时候将设施的名称埋入新的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$Stub$Proxy@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进来。
如果不是限度复原而是怕复原的数据被他人看到,须要加个验证爱护,怎么做?
譬如在复原数据完结之后存一个须要验证账号的Flag。当App关上的时候发现Flag的存在会强制验证账户,输出验证码等。
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") } } } }
键值对备份
键值对备份反对的空间小,而且针对File类型的Backup实现非线程平安,同时须要自行思考DB这种大空间文件的备份解决,并不举荐应用。
但本着学习的目标还是要理解一下。
根本定制
应用这个模式需额定指定BackupAgent并实现其细节。
<manifest ... > <application android:allowBackup\="true" android:backupAgent\=".MyBackupAgent" ... > <!-- 为兼容旧版本设施最好加上api\_key的meta-data --> <meta-data android:name\="com.google.android.backup.api\_key" android:value\="unused" /> </application\> </manifest\>
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。从日志能够看进去键值对备份和复原胜利进行了。
// 开启bmgr和设置本地传输服务 \>adb shell bmgr enabled \>adb shell bmgr transport com.android.localtransport/.LocalTransport // Backup \>adb shell bmgr backupnow com.ellison.backupdemo Running incremental backup for 1 requested packages. Package @pm@ with result: Success Package com.ellison.backupdemo with result: Success Backup finished with result: Success // 清空数据 \>adb shell pm clear com.ellison.backupdemo // 查看Backup Token \>adb shell dumpsys backup ... Ancestral: 0 Current: 1 // Restore \>adb shell bmgr restore 01 com.ellison.backupdemo Scheduling restore: Local disk image restoreStarting: 1 packages onUpdate: 0 = com.ellison.backupdemo restoreFinished: 0 done
Demo的截图显示File和SP备份和复原胜利了。但寄存在Data目录的海报和DB目录都失败了。这也验证了上述的论断。
因为出于备份文件空间的思考,官网并不倡议针对DB文件等大容量文件做键值对备份。实践上能够扩大FileBackupHelper对Data和DB文件做出反对。但Google将要害的备份实现(FileBackupHelperBase和performBackup\_checked())对外暗藏,使得简略扩大变得不可能。
StackOverFlow上针对这个问题有过热烈的探讨,惟一的方法是齐全本人实现,但随着主动备份的呈现,这个问题仿佛曾经不再重要。
Demo地址:
https://stackoverflow.com/que...
手动发动备份
BackupManager的dataChanged()函数能够告知零碎App数据变动了,能够安顿备份操作。咱们在Demo的Backup Button里增加调用。
class LocalData @Inject constructor(... val backupManager: BackupManager){ fun backupData() { backupManager.dataChanged() } ... }
点击这个Backup Button之后等几秒钟,发现Demo的备份工作被安顿进Schedule里,意味着备份操作将被零碎发动。
\>adb shell dumpsys backup Pending key/value backup: 3 BackupRequest{pkg=com.ellison.backupdemo} ★ ...
咱们能够强制这个Schedule的执行,也能够期待零碎的调度。
\>adb shell bmgr run
BackupManagerService: clearing pending backups PFTBT : backupmanager pftbt token=604faa13 ... BackupManagerService: \[UserID:0\] awaiting agent for ApplicationInfo{7b6a019 com.ellison.backupdemo} BackupRestoreAgent: onCreate() BackupManagerService: \[UserID:0\] agentConnected pkg=com.ellison.backupdemo agent=android.os.BinderProxy@be4cabf BackupManagerService: \[UserID:0\] got agent android.app.IBackupAgent$Stub$Proxy@4eab58c BackupRestoreAgent: onBackup() ★ BackupRestoreAgent: onDestroy() BackupManagerService: \[UserID:0\] Released wakelock:\*backup\*\-0-1265
手动发动复原
除了bmgr工具提供的restore以外还能够通过代码手动触发复原。但这并不平安会影响利用的数据一致性,所以复原的API requestRestore()废除了。
咱们来验证下,在Demo的Restore Button里增加BackupManager#requestRestore()的调用。
class LocalData @Inject constructor(... val backupManager: BackupManager){ fun restoreData() { backupManager.requestRestore(object: RestoreObserver() { ... }) } ... }
但点击Button之后等一段时间,复原的日志没有呈现,反倒是弹出了有效的正告。
BackupRestoreApp: LocalData#restoreData() BackupManager: requestRestore(): Since Android P app can no longer request restoring of its backup.
备份版本不统一的解决
版本不统一意味着复原之后的逻辑可能会受到影响,这是咱们在定制Backup性能时须要着重思考的问题。
版本不统一的状况有两种。
- 当初运行的利用版本比备份时候的版本高,比拟常见的场景
- 当初运行的利用版本比备份时候的版本低,即App降级,不太常见
默认状况下零碎会忽视App降级的复原操作,意味着BackupAgent#onRestore()永远不会被回调。
但如果利用对于旧版本数据的兼容解决比较完善,心愿反对降级的状况。那么须要在Manifest里关上restoreAnyVersion属性,零碎将意识到你的兼容并包并回调你的onRestore解决。
无论哪种状况都能够在BackupAgent#onRestore()回调里拿到备份时的版本。而后读取App以后的VersionCode,执行对应的数据迁徙或抛弃解决。
class MyBackupAgent: BackupAgentHelper() { ... override fun onRestore( data: BackupDataInput?, appVersionCode: Int, newState: ParcelFileDescriptor? ) { val packageInfo = packageManager.getPackageInfo(packageName, 0) if (packageInfo.versionCode != appVersionCode) { // Do something. // 能够调用BackupDataInput#restoreEntity() // 或skipEntityData()决定复原还是抛弃 } else { super.onRestore(data, appVersionCode, newState) } } }
间接扩大BackupAgent
扩大自BackupAgent的须要思考诸多细节,对这个计划有趣味的敌人能够参考BackupAgentHelper的源码,也能够查阅官网阐明。
零碎App的Backup限度
局部零碎App的隐衷级别较高,即使手动调用了Backup命令,零碎仍将忽视。并在日志中给出提醒。
BackupManagerService: Beginning adb backup... BackupManagerService: Starting backup confirmation UI, token=1763174695 BackupManagerService: Waiting for backup completion... BackupManagerService: acknowledgeAdbBackupOrRestore : token=1763174695 allow=true BackupManagerService: --- Performing adb backup --- BackupManagerService: Package com.android.phone is not eligible for backup, removing.★提醒该App不适宜备份操作 BackupManagerService: Adb backup processing complete. BackupManagerService: Full backup pass complete.
这个限度的源码在AppBackupUtils中,解决办法很简略在Manifest文件里明确指定BackupAgent。
其实Google的用意很分明,这些零碎级别的App数据要是被窃取将非常危险,默认禁止这个操作。但如果你指定了Backup代理那代表开发者思考到了备份和复原的场景,对这个操作进行了默认,备份操作才会被放行。
实战总结
Backup定制的总结
当咱们遇到Backup定制工作的时候认真思考下需要再隔靴搔痒。为使得这个流程更加直观,做了个流程图分享给大家。
Backup相干属性
结语
针对Backup性能的继续改善足以瞥见这个性能的重要性。开发者须要对这些改善放弃关注,一直调整Backup性能的开发策略,强化用户的数据安全。给大家一些实用倡议。
- 厂商针对Backup性能的Transport扩大能够是Google云盘也能够是国内服务器,App开发者须要关注本人的备份需要和安全策略
- 思考App是否反对备份,明确开关allowBackup属性
- 更为举荐空间更大、定制灵便的主动备份模式
- 尽快适配Android 12封堵数据泄露的危险
- 隐衷级别很高的数据能够补充设施加密的备份条件在备份阶段拦挡
- 复写BackupAgent能够退出复原的限度,灵便管制流程,在复原阶段二次拦挡
Demo地址:
https://github.com/ellisoncha...
- 提供了键值对备份模式的实现
- 针对主动备份模式预设了备份规定,并定制了限度备份源的复原流程
尾言
最初,心愿喜爱本文或者是本文对你有帮忙的敌人无妨点个赞,点个关注,你的反对是我更新的最大能源!!!