
allowBackup屬性大家都不陌生,為了安全起見最好將它關閉,對它的認識好像也僅限于此了,事實上Google在Backup功能上花了很多心思,提供了多個模式的選擇和充分的定制介面,本文將帶大家全面復盤Backup功能的由來、原理和定制方法,以備不時之需,篇幅較長,配合收藏更佳,
為節省部分讀者的時間,貼出重點章節供快速空降,
- 關心Backup功能
原理的,直接空降2.8章節 - 關心Backup功能
測驗的,直接空降3.2章節 - 關心Backup檔案
破解的,直接空降3.3章節 - 關心Backup
實戰的,直接空降4.0章節 - 關心
Android 12影響的,直接空降5.0章節
1. 前言
兩年前我就遇到過一個Backup功能相關的CTS問題,說的是整機恢復到AccessibilitySerivce的時候發生錯誤,整機備份和恢復非常耗時,我不可能真的跑一遍去定位問題,我得找個高效的辦法,
通過查閱Backup的原理我知道了可以單獨Backup和Restore某個app,甚至可以解密備份檔案查看資料內容,有了這些方法的協助,我很快就找到了原因,
雖然很快地解決了那個問題,但我的心里留下了一個想法,Backup功能好像比我想象的復雜有趣,一定要找個時間好好了解一下,恰逢近期在做Backup功能的定制,對這塊有了充分的認識,便整理出來分享給大家,
2. 完整認識Backup
2.1 功能由來
手機等智能設備是現代生活中的重要角色,我們會在這些智能設備上做登錄賬戶,設定偏好,拍攝照片,保存聯系人等日常操作,
這些資料耗費了我們很多時間和精力,對我們而言極為重要,如果我們的設備換代了或者重新安裝了某個應用,之前使用的資料如果能自動保留,那將是非常出色的用戶體驗,而保留資料的第一步則在于Backup環節,
2.2 資料來源
用戶的資料可以籠統地劃分為三塊:登錄賬號相關的身份資料、系統設定相關的偏好以及各App的資料,這三塊資料的型別不同、位置不同,進而導致Backup的實作也不同,

- App資料:應用內部的圖片,視頻等資料,這是我們尤為關心的資料,如何安全完整地轉移這些資料是Backup功能的目標所在,也是本文需要講解的核心內容
- 身份資料:用戶登錄的身份資料,可以通過Smart Lock或Account Transfer API在設備間立即恢復登錄狀態
- 設定偏好:系統設定App和SettingProvider將記錄用戶的偏好資料,甚至包括用戶授予App的權限記錄,系統將針對這些設定資料備份和恢復
2.3 備份物件
我們知道可以將資料存放在App目錄,也可以存放于公共目錄,但隨著Android系統針對公共目錄的限制愈加嚴格,將資料存放到App自己的目錄顯得更加合理,
App自身目錄的這塊資料順理成章地成為Backup功能的主要物件,按照檔案的型別可以細分如下,
| 型別 | 路徑 | 取得對應檔案的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() |
注意:
- 放置在外部存盤空間中的檔案也是支持的,這里不再贅述
- cache、nobackup等目錄下的檔案不在Backup物件內
Backup操作從最外層的data目錄開始,按照檔案單位逐個讀取逐個備份,目錄內的檔案一般按照檔案名的順序進行備份,但這個順序無法保證,取決于File#list() API的結果,
2.4 如何開啟
在Manifest檔案里使用allowBackup屬性可以控制Backup功能的開關,這個屬性早在Android 1.6(API 4)的時候便引入了,起初默認是關閉的,
allowBackup
Added in API level 4
Whether to allow the application to participate in the backup and restore infrastructure. If this attribute is set to false, no >backup or restore of the application will ever be performed, even by a full-system backup that would otherwise cause all >application data to be saved via adb. The default value of this attribute is true.
可能是Android設備火了,備份恢復的需求越來越大,自Android 6.0之后默認值變成了true,即默認將支持Backup功能,
2.5 開啟的隱患
allowBackup屬性開啟的話,開發者使用幾個簡單的adb命令就可以將應用的資料備份和恢復,意味著自己的資料被轉移到別人的設備上是非常簡單的,這無疑造成了具大的安全隱患,
后面的實戰將會揭曉控制備份和恢復的諸多可能,比如在恢復的邏輯里加入了限制使得惡意的恢復失敗,但即便在恢復階段攔截了,備份的ab檔案仍掌握在別人手里,仍不穩妥,因為通過破解備份檔案也可以閱讀到部分甚至全部內容,
所以涉及到私密資料的App最好將該屬性關閉,自行考慮資料的同步方式,比如通過賬號系統恢復,
這個屬性曾經引發安全隱患,詳情可參考:https://blog.csdn.net/zihao2012/article/details/44220389
2.6 備份模式
Android 6.0之前Backup功能只有鍵值對備份(Key-value Backup)這一種模式,而且默認是關閉的,
想要打開鍵值對備份功能得將allowBackup屬性設定為true,并指定BackupAgent實作,即明確地告知訴Backup功能每個檔案按照什么key備份到Android Backup Service,簡單來講,必須給Backup功能提供一個備份檔案的映射關系,好讓它知道備份的源頭和恢復的目標,
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() |
2.7 備份的托管位置
使用鍵值對模式備份的檔案托管在Google服務器(Android Backup Service),Google承諾將會加密傳輸這些資料,并尊重隱私條款,同時在App關閉Backup功能的時候洗掉這些備份,可以放心使用,
如果采用了默認的自動備份模式,那資料存放在Google Drive云盤,云盤擁有自己的賬號系統,使用的是賬號級別的加密保護,更為安全,
2.8 實作原理
那Android系統是如何實作Backup和Restore功能的呢?
在解答這個問題之前,我們先思考下如果你是Google開發者,你會怎么實作?
這里有個ContentProvider方案,簡言之,使用一個統一調度的App通過ContentProvider組件向各個實作了特定Uri或Permission的ContentProvider App發出讀寫資料的請求,
- 各App通過ContentProvider將需要備份的Data、File、DB以及SP檔案傳輸出去,調度App收集到包名為單位的備份檔案集合,加密后上傳到服務器或記憶體卡
- 恢復則是調度App將檔案解密之后通過ContentProvider再回傳給各App,各App自行執行恢復的邏輯
但這個方案有點缺憾,對于大部分App來說檔案全部備份和恢復就行了,不需要搞特別的定制,但實際情況是需要支持備份恢復的話,就得各自實作一套ContentProvider去做檔案的收集和覆寫,
而Google采取的方案是這樣的,默認認為每個App都支持Backup功能,然后給App提供同樣的BackupAgent去執行自動備份和恢復的處理,當App存在特別定制的需求的時候可以指定擴展的BackupAgent邏輯,更加靈活高效,
內部的實作原理簡述如下,
- 系統服務BMS(
BackupManagerService)收到BackupManagerAPI發起的備份/恢復請求后,該服務將通過IBackupTransport和Cloud端建立連接 - BMS通過持有的
BackupHandler依據操作引數啟動相應的Backup或Restore執行緒 - 任務執行緒通過
AppBackupUtils檢查該App是否支持Backup/Restore - 之后依據備份模式創建對應的Engine并通過通過
IBackupAgent向App發起實際的操作請求 BackupAgent將按照對應模式去讀取或寫入檔案

3. 發起、除錯和解密Backup
3.1 Backup/Restore的發起
3.1.1 代碼方式
選取了鍵值對備份模式的話,需要在資料發生變動的時候手動發起備份,SDK提供了API:BackupManager的dataChanged(),呼叫之后BackupManager將調度備份請求在適當的時機發起備份處理,
事實上BackupManager還提供了requestRestore()供我們手動發起恢復,API的回傳值將告訴我們是否將要執行恢復操作,這個API發起的恢復結束之后不會KILL行程,存在造成資料錯亂的隱患,最好依賴于系統自行的恢復操作,
※自Android 9.0開始這個API廢棄了,呼叫了也沒有反應,
3.1.2 命令方式
ⅰ. adb命令
adb的backup和restore命令可以幫助我們手動發起較為簡單的請求,但只支持自動備份模式,
- Backup
// 比如備份某個App的資料并以指定的名稱保存備份檔案
adb backup -f <fileName>.ab -apk <packageName>
接下來系統會提示我們輸入備份密碼,

輸完密碼之后點擊開始備份,系統將彈出備份開始或結束的Toast,當然不輸入密碼直接備份也是可以的,但備份的資料容易被破解,
- Restore
// 發起恢復請求的命令很簡單
adb restore <fileName>.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 <package>
在另一個終端捕捉備份的執行日志,有可能會提示沒有設定鎖屏密碼,
Backup : [CryptoEnableCheck] Should not encrypt backups: device has no lock screen.
設定密碼后再次發起備份,可以看到成功備份了,
>adb shell bmgr backupnow <package>
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$Stub$Proxy@c309ea7
BackupRestoreAgent: onBackup()
BackupRestoreAgent: onDestroy()
bmgr工具在手動恢復的時候需要Token資訊,通過dumpsys backup獲取對應的Token,Token來自于Ancestral和Current兩個標簽的組合,比如本次的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 <package>
手動恢復資料,從命令和日志兩個終端都能看到資料被正確恢復了,
>adb shell bmgr restore 01 <package>
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$Stub$Proxy@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使用方法可參考如下檔案,
https://developer.android.google.cn/studio/command-line/bmgr?hl=zh-cn
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
我們先做個涉及到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


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" ... />
</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"/>
<!-- path里指定.代表該目錄下所有檔案都適用這個規則,免去逐個指定各個檔案 -->
<include domain="sharedpref" path="."/>
<!-- exclude指定不參與備份的檔案 -->
<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進來,
ⅵ.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" ... >
<!-- 為兼容舊版本設備最好加上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上針對這個問題有過熱烈的討論,唯一的辦法是完全自己實作,但隨著自動備份的出現,這個問題似乎已經不再重要,
https://stackoverflow.com/questions/5282936/android-backup-restore-how-to-backup-an-internal-database#
ⅱ.手動發起備份
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的原始碼,也可以查閱官方說明,
https://developer.android.google.cn/guide/topics/data/keyvaluebackup
4.5 系統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代理那代表開發者考慮到了備份和恢復的場景,對這個操作進行了默許,備份操作才會被放行,
4.6 實戰總結
4.6.1 Backup定制的總結
當我們遇到Backup定制任務的時候認真思考下需求再對癥下藥,為使得這個流程更加直觀,做了個流程圖分享給大家,

4.6.2 Backup相關屬性
| 相關屬性 | 說明 |
|---|---|
| allowBackup | 是否支持Backup,默認為true |
| backupAgent | 指定Backup代理進行定制 |
| fullBackupContent | 指定備份規則XML檔案 |
| restoreAnyVersion | 是否支持高版本資料恢復到低版本應用,默認為false |
| fullBackupOnly | 在指定了BackupAgent后仍然采用AutoBackup模式 |
| killAfterRestore | 全系統恢復期后是否終止應用,默認為 true |
| backupInForeground | 即使應用處于前臺也可以對其執行自動備份,默認為false |
| clientSideEncryption | 只在手機設定密鑰的情況下執行備份 |
| deviceToDeviceTransfer | 只在D2D設備間備份的情況下執行備份 |
5. Android 12的影響和Backup功能的發展歷程
Android 12 Beta版即將公開,其針對Backup功能又做了些改動,先來看看變更的說明,
5.1 D2D 設備到設備備份的規則細分
For apps running on and targeting
Android 12and higher:
- Specifying
android:allowBackup="false"does disable backups to Google Drive, but doesn’t disableD2Dtransfers for the app.- Specifying
includeandexcluderules with the XML configuration mechanism no longer affects D2D transfers, though it still affects Google Drive backups. To specify rules for D2D transfers, you must use the new configuration covered in the next section.
簡直之,Android 12開始即便關閉了allowBackup屬性,D2D的Backup功能仍將有效,不再受影響,同時原有的通過fullBackupContent指定的配置規則也將失效,
如果你的App目標版本是Android 12的話,需要使用新屬性dataExtractionRules來指定語法規則,
語法規則的所變化主要體現在使用新的屬性cloud-backup和device-transfer明示地區分云端備份和D2D備份的規則,而不再像之前那樣采用full-backup-content指定統一的規則,
另外原有的設備條件flag也發生了變化,
- clientSideEncryption:在新規則里變成了
disableIfNoEncryptionCapabilities,且只能應用在cloud-backup標簽內 - deviceToDeviceTransfer:新規則將D2D區分開來了,所以這個flag不需要了
<application
android:dataExtractionRules="new_config.xml"
...>
</application>
<data-extraction-rules>
<cloud-backup [disableIfNoEncryptionCapabilities="true|false"]>
<include domain=["file" | "database" | "sharedpref" | "external" |
"root"] path="string"/>
...
</cloud-backup>
<device-transfer>
<include domain=["file" | "database" | "sharedpref" | "external" |
"root"] path="string"/>
...
</device-transfer>
</data-extraction-rules>
原因在于云端備份存在空間的限制,難免需要對備份的檔案做出取舍,而D2D的場景檔案是存在本地的,沒有這種限制了卻還對備份檔案做出削減顯然不太合理,
具體細節可參考官方檔案,
https://developer.android.google.cn/about/versions/12/backup-restore
5.2 adb backup命令的限制
To help protect private app data, Android 12 changes the default behavior of the adb backup command. For apps that target Android 12, when a user runs the adb backup command, app data is excluded from any other system data that is exported from the device.
adb backup命令是可以備份整機資料的,從Android 12開始該資料里將不包含App部分的應用資料,除非在Manifest里手動打開debuggable屬性,
如果備份單個App也失敗的話,那安全性將大大提高,筆者在12 Preview版本上執行該命令仍舊能夠正常備份,不知道是不是Target SDK的問題,等正式版出來后再嘗試下,
詳情可參考官方說明,
https://developer.android.google.cn/about/versions/12/behavior-changes-12
5.3 Backup功能的發展歷程
簡要回顧下Backup功能的發展歷程,供快速查閱,
| 版本 | 變化內容 |
|---|---|
| Android 1.6 | 加入allowBackup屬性默認關閉 |
| Android 2.2 | 開始使用鍵值對備份模式 |
| Android 6.0 | 開始支持自動備份模式,默認打開allowBackup屬性 |
| Android 7.0 | Backup功能將自動備份和恢復用戶授予App的權限 |
| Android 9.0 | 新增了加密存盤備份檔案 |
| Android 12 | D2D場景的Backup規則變更和adb backup命令的限制 |
6. 結語
Google針對Backup功能的頻繁改動可以看出來其對于這個功能的重視,總結起來就是在功能的易用性,安全性,合理性之間反復優化,
針對這些變化開發者需要不斷調整Backup功能的開發策略,我也給出一些實用建議,
- 思考App是否支持備份,明示地設定allowBackup屬性
自動備份模式提供的備份空間更大,定制靈活,更為推薦- 隱私級別很高的資料可以添加
設備加密的備份條件 - 復寫
BackupAgent可以靈活定制備份和恢復的流程,值得好好研究 - 出于學習和調查的目的可以嘗試了解和
破解Backup檔案 - backup命令已不推薦,除錯Backup功能盡量嘗試功能更為強大的
bmgr工具
未決懸念
- 官方檔案說明鍵值對備份從2.2開始提供支持,可是
allowBackup屬性自1.6便于匯入,那在2.2之前的備份采取哪種模式呢?
想找到一臺2.2以前的設備去驗證動作不太現實,打算在2.2之前的系統原始碼里找到答案, - Android 12上目標SDK為12的話如果debuggable未開的話,無論備份整機還是單個app都將失敗?
- clientSideEncryption表示Backup功能打開且設定了密碼均可開始執行備份,但實際測驗不是,總是沒有執行備份,
DEMO
https://github.com/ellisonchan/BackupRestoreApp
參考資料
備份功能的官方主頁
鍵值對備份模式
自動備份模式
測驗備份和恢復
bmgr工具
allowBackup造成的安全問題
Backup檔案解密JAR包
Backup檔案的決議
鍵值對備份模式的DB支持
Android 12的行為變更
推薦閱讀
Jetpack Hilt有哪些改善又有哪些限制?
Dagger2和它在SystemUI上的應用
除了SQLite一定要試試Room
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/280725.html
標籤:其他
