前言

手机等智能设施是古代生存中的重要角色,咱们会在这些智能设施上做登录账户,设置偏好,拍摄照片,保留联系人等日常操作。这些数据消耗了咱们很多工夫和精力,对咱们而言极为重要。

如果咱们的设施换代了或者重新安装了某个利用,之前应用的数据如果能主动保留,那将是十分杰出的用户体验。而保留数据的第一步则在于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传回来,所以判断利用版本的方法行不通。而且有的时候利用版本是统一的,只是运营商不统一。

所以须要咱们本人实现,大家能够自行思考。先说我之前想到的几种计划。

  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$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性能时须要着重思考的问题。

版本不统一的状况有两种。

  1. 当初运行的利用版本比备份时候的版本高,比拟常见的场景
  2. 当初运行的利用版本比备份时候的版本低,即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性能的开发策略,强化用户的数据安全。给大家一些实用倡议。

  1. 厂商针对Backup性能的Transport扩大能够是Google云盘也能够是国内服务器,App开发者须要关注本人的备份需要和安全策略
  2. 思考App是否反对备份,明确开关allowBackup属性
  3. 更为举荐空间更大、定制灵便的主动备份模式
  4. 尽快适配Android 12封堵数据泄露的危险
  5. 隐衷级别很高的数据能够补充设施加密的备份条件在备份阶段拦挡
  6. 复写BackupAgent能够退出复原的限度,灵便管制流程,在复原阶段二次拦挡

Demo地址:

https://github.com/ellisoncha...
  • 提供了键值对备份模式的实现
  • 针对主动备份模式预设了备份规定,并定制了限度备份源的复原流程

尾言

最初,心愿喜爱本文或者是本文对你有帮忙的敌人无妨点个赞,点个关注,你的反对是我更新的最大能源!!!