資料備份
快速檢視
· 将使用者資料備份到雲中心以防丢失。
· 如果使用者更新到運作android的新裝置,程式可以恢複使用者資料到新裝置中。
· 可友善地用backupagenthelper備份sharedpreference和私有檔案。
· 需要api level 8支援。
在本文中
基本情況
在manifest中聲明備份代理
為android備份服務進行注冊
繼承backupagent
必需的方法
執行備份
執行恢複
繼承backupagenthelper
備份sharedpreferences
備份私有檔案
檢查恢複資料的版本
請求備份
請求恢複
測試備份代理
關鍵類
backupmanager
backupagent
backupagenthelper
參閱
bmgr tool
為了給應用程式的資料和配置資訊提供資料還原點,android的備份backup服務允許把需持久儲存的資料拷貝到遠端“雲”存儲中。如果使用者恢複了出廠設定或者換用新的android裝置,系統将在再次安裝應用程式時自動恢複備份資料。這樣,就不需要使用者複制之前的資料和程式配置資訊。整個過程對于使用者而言完全透明,不會影響程式的功能和使用者體驗度。
在備份過程中(應用程式可發起請求),android的備份管理器(backupmanager)将查找應用程式中需備份的資料,并把資料交給備份傳輸器,傳輸器再把資料傳送給雲存儲。在恢複時,備份管理器從備份傳輸器取回備份資料并将其傳回給應用程式,然後應用程式就能把資料恢複到裝置上。應用程式也能夠發起恢複請求,但不是必須的——如果程式安裝完畢且存在使用者相關的備份資料,android會自動執行恢複操作。恢複備份資料主要發生于以下場合:使用者重置裝置或者更新到新裝置後,以前裝過的應用程式又被再次安裝。
注意:備份服務并不是為以下用途設計的:與其它用戶端同步、在程式正常生命周期内儲存資料。備份資料不允許随意讀寫,除通過備份管理器提供的api外無法通路資料。
備份傳輸器是android備份架構的用戶端元件,它可由裝置制造商和提供商定制。備份傳輸器可以因裝置不同而不同,對于應用程式而言它是透明的。備份管理器的api将應用程式和實際備份傳輸器聯接起來——程式通過一組固定的api與備份管理器進行通訊,而不必關心底層的傳輸過程。
并不是所有android平台的裝置都能支援資料備份。不過,即使裝置不支援備份傳輸,對程式運作也不會有什麼影響。如果确信使用者将受益于資料備份服務,隻管按照本文所述去實作、測試并釋出即可,而不必去關心哪些裝置會真正執行備份工作。就算是在不支援備份傳輸的裝置上,程式仍然會正常運作,隻是不能接收備份管理器的請求進行資料備份而已。
盡管對目前所傳輸内容一無所知,但盡管放心,備份資料是不能被裝置上的其它程式讀取的。在備份過程中,隻有備份管理器和備份傳輸器有權限通路被送出的資料。
警告:因為雲存儲和傳輸服務依裝置而各不相同,android不保證使用備份服務資料的安全性。如果要利用備份服務儲存敏感資料(比如使用者名和密碼),應該始終保持謹慎态度。
為了備份應用程式資料,需要實作一個備份代理。此備份代理将被備份管理器調用,用于提供所需備份的資料。當程式重裝時,還要調用此代理來恢複資料。備份管理器處理所有與雲存儲之間的資料傳輸工作(利用備份傳輸器),備份代理則負責所有對裝置上資料的處理。
要實作備份代理,必須:
1. 在manifest檔案内用android:backupagent屬性聲明備份代理。
2. 用備份服務對應用程式進行注冊。google為大多數android平台的裝置提供了android備份服務
,必須對應用程式進行注冊以便服務生效。為了在它們的伺服器上存儲資料,其它所有的備份服務提供方也都可能需要注冊。
3. 用以下兩種方式之一進行備份代理的定義:
a) 繼承backupagent
backupagent類提供了核心接口,程式通過這些接口與備份管理器進行通訊。如果直接繼承此類,必須覆寫onbackup()和onrestore()方法來處理資料的備份和恢複操作。
b) 繼承backupagenthelper
backupagenthelper類提供了backupagent類的易用性封裝,它減少了需編寫的代碼數量。在backupagenthelper内,必須用一個或多個“helper”對象來自動備份和恢複特定類型的資料,是以不再需要實作onbackup()和onrestore()方法了。
android目前提供兩種backup helper,用于從sharedpreferences和internal
storage備份和恢複整個的檔案。
這是最容易的一步,一旦确定了類名,就可在manifest的<application>
标簽内用android:backupagent屬性聲明備份代理了。
例如:
<manifest ...
>
...
<applicationandroid:label="myapplication"
android:backupagent="mybackupagent">
<activity ...
...
</activity>
</application>
</manifest>
其它可能會用到的屬性是android:restoreanyversion。這個屬性用布爾值标明恢複資料時是否忽略目前程式和産生備份資料的程式之間的版本差異(預設值是“false”)。詳情請參閱檢查恢複資料的版本。
注意:備份服務和api隻在運作api level 8(android 2.2)以上版本的裝置上才可用,是以應把android:minsdkversion
屬性設為“8”。當然,如果程式實作了良好的向後相容性,可以僅針對api level 8以上版本的裝置提供備份功能,而對其它舊版本裝置則保持相容即可。
google為大多數android 2.2以上版本的裝置提供了利用android備份服務進行的備份傳輸服務。
為了程式能利用android備份服務執行備份操作,必須對程式進行注冊以獲得一個backup service key,然後在android manifest檔案中聲明這個key。
要擷取backup service key,請為android服務進行注冊。注冊時會得到一個backup
service key和android manifest檔案内相應的<meta-data> xml代碼,這段代碼必須包含在<application>元素下。例如:
<applicationandroid:label="myapplication"
android:backupagent="mybackupagent">
<meta-dataandroid:name="com.google.android.backup.api_key"
android:value="aedpqreaaaaidayevgu6djnyjdbmu7klh3kszdxlv_4diseiyq"/>
</application>
android:name必須是"com.google.android.backup.api_key",android:value也必須是注冊android備份服務時收到的backup service key。
如果存在多個應用程式,必須根據各自的程式包名稱(package name)為每一個程式進行注冊。
注意:即使裝置能夠支援,android備份服務提供的備份傳輸器也不一定在所有android 平台的裝置上都能執行。有些裝置可能使用不同的傳輸器來為備份提供支援,有些裝置可能根本就不支援備份,程式是無法知道裝置使用何種傳輸器的。不過,假如為程式實作了備份,就必須為備份服務指定backup service key,這樣裝置利用android備份服務進行傳輸時程式就能順利執行備份工作。如果裝置不使用android備份服務,帶backup service
key的<meta-data>元素将被忽略。
大多數應用程式應該不需要直接繼承使用backupagent 類,取而代之的是繼承backupagenthelper類,并利用backupagenthelper内建的helper類自動備份和恢複檔案。不過,如果需要實作以下目标的話,也許希望能直接繼承backupagent:
· 将資料格式版本化。例如需要在恢複資料時修正格式,可以建立一個備份代理,在資料恢複過程中如果發現目前版本和備份時的版本不一緻,可以執行必要的相容性修正工作。詳情請參閱檢查恢複資料的版本。
· 不是備份整個檔案,而是指定備份部分資料及指定恢複各部分資料。(這也有助于管理不同版本的資料,因為是把資料作為唯一entity來讀寫,而不是讀寫整個檔案。)
· 備份資料庫中的資料。如果用到sqlite資料庫并且希望使用者重裝系統時能恢複其中資料,需要建立自定義的backupagent。它在備份時讀取庫中資料,而在恢複時建表并插入資料。
如果不需要執行以上的任務,而隻是從sharedpreferences或内部存儲備份完整的檔案,請跳轉到繼承backupagenthelper。
通過繼承backupagent建立備份代理時,必須實作以下回調方法:
onbackup()
備份管理器在程式請求備份後将調用本方法。如下文執行備份所述,在本方法中實作從裝置讀取應用程式資料,并把需備份的資料傳遞給備份管理器。
onrestore()
備份管理器在恢複資料時調用本方法(也可以主動請求恢複,但在使用者重裝應用程式時系統會自動執行資料恢複。)如下文執行恢複所述,備份管理器調用本方法時将傳入備份的資料,然後就可把資料恢複到裝置上。
備份應用程式資料時,備份管理器将調用onbackup()方法。在此方法内必須把資料提供給備份管理器,然後資料被儲存到雲存儲中。
隻有備份管理器能夠調用備份代理中的onbackup()方法。每當資料發生改變并需要執行備份時,必須調用datachanged()發起一次備份請求(詳情請參閱請求備份)。備份請求并不會立即導緻onbackup()方法的調用。備份伺服器會等待合适的時機,為上次備份操作後又發出備份請求的所有應用程式執行備份。
提示:在開發應用程式的過程中,可以用bmgr工具讓備份管理器立即執行備份操作。
當備份管理器調用onbackup()方法時,傳入以下三個參數:
oldstate
已打開的、隻讀的檔案描述符parcelfiledescriptor,指向應用程式提供的有關上次備份資料狀态的檔案。這不是來自雲存儲的備份資料,而是記錄上次調用onbackup()所備份資料相關狀态資訊的本地檔案(如下文newstate所定義,或來自下節onrestore())。因為onbackup()不允許讀取儲存于雲存儲的資料,可以根據此資訊來判斷資料自上次備份以來是否變動過。
data
backupdataoutput對象,用于将備份資料傳給備份管理器。
newstate
已打開的、可讀寫的檔案描述符parcelfiledescriptor,指向一個檔案,必須将送出給data參數的資料相關狀态資訊寫入此檔案(此狀态資訊可以簡單到隻是檔案的最後修改時間戳)。備份管理器下次調用onbackup()時,本對象作為oldstate傳入。如果沒有往newstate寫入資訊,則備份管理器下次調用onbackup()時oldstate将指向一個空檔案。
利用以上參數,可以實作onbackup()方法如下:
1. 通過比較oldstate,檢查自上次備份以來資料是否發生改變。從oldstate讀取資訊的方式取決于當時寫入的方式(見第3步)。最簡單的記錄檔案狀态的方式是寫入檔案的最後修改時間戳。以下是如何從oldstate讀取并比較時間戳的例子:
//
擷取oldstate輸入流
fileinputstream instream
=newfileinputstream(oldstate.getfiledescriptor());
datainputstreamin=newdatainputstream(instream);
try{
//
從state檔案和資料檔案擷取最後修改時間戳
long statemodified
=in.readlong();
long filemodified
= mdatafile.lastmodified();
if(statemodified
!= filemodified){
// the file has been modified, so do a backup
// or the time on the device changed, so be safe and do a backup
}else{
// don't back up because the file hasn't changed
return;
}
}catch(ioexception e){
// unable to read state file... be safe and do a backup
}
如果資料沒有發生變化,就不需要進行備份,請跳轉到第3步。
2. 在和oldstate比較後,如果資料發生了變化,則把目前資料寫入data以便将其傳回并上傳到雲存儲中去。
必須以backupdataoutput中的“entity”方式寫入每一塊資料。一個entity是用一個唯一字元串鍵值辨別的拼接二進制資料記錄。是以,所備份的資料集其實上是一組鍵值對。
要在備份資料集中增加一個entity,必須:
1. 調用writeentityheader(),傳入代表寫入資料的唯一字元串鍵值和資料大小。
2. 調用writeentitydata(),傳入存放資料的位元組類型緩沖區,以及需從緩沖區寫入的位元組數(必須與傳給writeentityheader()的資料大小一緻)。
例如,以下代碼把一些資料拼接為位元組流并寫入一個entity:
為資料建立緩沖區流和輸出流
bytearrayoutputstream bufstream
=newbytearrayoutputstream();
dataoutputstream outwriter
=newdataoutputstream(bufstream);
寫入結構化的資料
outwriter.writeutf(mplayername);
outwriter.writeint(mplayerscore);
通過backupdataoutput 發送資料到備份管理器backup manager
byte[] buffer
= bufstream.tobytearray();
int len
= buffer.length;
data.writeentityheader(topscore_backup_key,
len);
data.writeentitydata(buffer,
len);
對每一塊需備份的資料都要執行以上操作。程式負責把資料切分為多個entity(當然也可以隻用一個entity)。
3. 無論是否執行備份(第2步),都要把目前資料的狀态資訊寫入newstate
parcelfiledescriptor指向的檔案内。備份管理器會在本地保持此對象,以代表目前備份資料。下次調用onbackup()時,此對象作為oldstate傳回給應用程式,由此可以決定是否需要再做一次備份(如第1步所述)。如果不把目前資料的狀态寫入此檔案,下次調用時oldstate将傳回空值。
以下例子把檔案最後修改時間戳作為目前資料的狀态存入newstate:
fileoutputstream outstream = new fileoutputstream(newstate.getfiledescriptor());
dataoutputstream out = new dataoutputstream(outstream);
long modified = mdatafile.lastmodified();
out.writelong(modified);
警告:如果應用程式資料存放于檔案中,請確定使用同步語句(synchronized)來通路檔案。這樣在應用程式的activity寫檔案時,備份代理就不會去讀檔案了。
恢複程式資料時,備份管理器将調用備份代理的onrestore()方法。調用此方法時,備份管理器會把備份的資料傳入,以供恢複到裝置中去。
隻有備份伺服器能夠調用onrestore(),在系統安裝應用程式并且發現有備份資料存在時,調用會自動發生。不過,也可以通過調用requestrestore()來發起恢複資料的請求(詳情參閱請求恢複)。
注: 在開發應用程式的過程中,可以用bmgr工具發起恢複資料的請求。
當備份管理器調用onrestore()方法時,傳入以下三個參數:
backupdatainput對象,用以讀取備份資料。
appversioncode
整數,表示備份資料時應用程式manifest中的android:versioncode屬性。可以用于核對目前程式版本并确定資料格式的相容性。關于利用此版本号來處理不同版本恢複資料的詳細情況,請參閱下文檢查恢複資料的版本。
已打開的,可讀寫的檔案描述符parcelfiledescriptor,指向一個檔案,這裡必須寫入最後一次送出data資料的備份狀态。本對象在下次調用onbackup()時作為oldstate傳回。回想一下,onbackup()方法也必須寫入newstate
對象——這裡也同樣要這麼做。這樣即使裝置重置後第一次調用onbackup(),也能確定有可用的oldstate對象能傳給onbackup()方法。
在實作onrestore()時,應該對data
調用readnextheader(),以周遊資料集裡所有的entity。對其中每個entity須進行以下操作:
1. 用getkey()擷取entity的鍵值。
2. 将此entity鍵值和已知鍵值清單進行比較,這個清單應該已經在backupagent繼承類中作為字元串常量(static
final string)進行定義。一旦鍵值比對其中一個鍵,就執行讀取entity資料并儲存到裝置的語句:
1. 用getdatasize()讀取entity資料大小并據其建立位元組數組。
2. 調用readentitydata(),傳入位元組數組作為擷取資料的緩沖區,并指定起始位置和讀取位元組數。
3. 位元組數組将被填入資料,按需讀取資料并寫入裝置即可。
3. 把資料讀出并寫回裝置以後,和上面onbackup()過程類似,把資料的狀态寫入newstate參數。
下面是把前一節例子中所備份的資料進行恢複的示例:
@override
publicvoid onrestore(backupdatainput
data,int appversioncode,
parcelfiledescriptor newstate)throwsioexception{
應該是隻有一個entity,
但最安全的方法還是用循環來處理
while(data.readnextheader()){
string key = data.getkey();
int datasize
= data.getdatasize();
// 如果鍵值是所需的(儲存top score),注意這個鍵值是用于
// 寫入備份entity header
if(topscore_backup_key.equals(key)){
// 為backupdatainput建立輸入流
byte[] databuf
=newbyte[datasize];
data.readentitydata(databuf,0,
datasize);
bytearrayinputstream bastream
=newbytearrayinputstream(databuf);
datainputstreamin=newdatainputstream(bastream);
// 從備份資料中讀取player name和score
mplayername
=in.readutf();
mplayerscore
=in.readint();
// record the score on the device (to a file or something)
recordscore(mplayername, mplayerscore);
}else{
// 不知道這個entity鍵值,跳過,(這本不該發生)
data.skipentitydata();
}
// finally, write to the state blob (newstate) that describes the restored data
fileoutputstream outstream
=newfileoutputstream(newstate.getfiledescriptor());
dataoutputstreamout=newdataoutputstream(outstream);
out.writeutf(mplayername);
out.writeint(mplayerscore);
在以上例子中,傳給onrestore()的appversioncode參數沒有被用到。假如使用者程式的版本已經降級(比如從1.5降到1.0),可能就會用此參數來選擇備份資料。更多資訊請參閱檢查恢複資料的版本。
關于backupagent的完整例子,請參閱例程備份和恢複中的exampleagent類。
如果要備份整個檔案(來自sharedpreferences或内部存儲),應該用backupagenthelper建立備份代理來實作。因為不必實作onbackup()和onrestore()了,用backupagenthelper建立備份代理所需的代碼量将遠遠少于backupagent。
backupagenthelper的實作必須要使用一個或多個backup helper。backup helper是一種專用元件,backupagenthelper用它來對特定類型的資料執行備份和恢複操作。android架構目前提供兩種helpers:
· sharedpreferencesbackuphelper用于備份sharedpreferences檔案。
· filebackuphelper用于備份來自内部存儲的檔案。
在backupagenthelper中可包含多個helper,但對于每種資料類型隻需用到一個helper
。也就是說,即使存在多個sharedpreferences檔案,也隻需要一個sharedpreferencesbackuphelper。
對于每個要加入backupagenthelper的helper,必須在oncreate()
中執行以下步驟:
1. 執行個體化所需的helper。在其構造方法裡必須指定需備份的檔案。
2. 調用addhelper()把helper加入backupagenthelper。
下一節描述了如何使用每種helper建立備份代理。
執行個體化sharedpreferencesbackuphelper時,必須包括一個或多個sharedpreferences
檔案。
例如,假設需備份的sharedpreferences檔案名為“user_preferences”,完整的使用backupagenthelper的備份代理代碼類似如下:
publicclassmyprefsbackupagentextendsbackupagenthelper{
// sharedpreferences
檔案名
staticfinalstring prefs
="user_preferences";
唯一辨別備份資料的鍵值
staticfinalstring prefs_backup_key
="prefs";
申請helper并加入備份代理
@override
publicvoid oncreate(){
sharedpreferencesbackuphelper helper
=newsharedpreferencesbackuphelper(this,
prefs);
addhelper(prefs_backup_key, helper);
好,這就是一個備份代理的完整實作。sharedpreferencesbackuphelper内含了備份和恢複sharedpreferences檔案的所有代碼。
當備份管理器調用onbackup()和onrestore()時,backupagenthelper調用helper來對給定檔案執行備份和恢複操作。
注:sharedpreferences是線程安全的,是以可以從備份代理和其它activity中安全地讀寫shared
preferences檔案。
備份其它檔案
在執行個體化filebackuphelper時,必須包含一個或多個儲存于程式内部存儲中的檔案名稱。(路徑的描述方式類似getfilesdir(),并且作為openfileoutput()寫入檔案的路徑。)
比如,需要備份兩個名為“scores”和“stats”的檔案,備份代理使用backupagenthelper示例如下:
publicclassmyfilebackupagentextendsbackupagenthelper{
// sharedpreferences檔案的名稱
staticfinalstring top_scores
="scores";
staticfinalstring player_stats
="stats";
唯一辨別備份資料集的鍵值
staticfinalstring files_backup_key
="myfiles";
void oncreate(){
filebackuphelper helper
=newfilebackuphelper(this,
top_scores, player_stats);
addhelper(files_backup_key, helper);
filebackuphelper包含了備份和恢複存于内部存儲的檔案所需的全部代碼。
但是,讀寫内部存儲檔案不是線程安全的。要確定activity操作檔案的時候備份代理不會去讀寫檔案,每次讀寫檔案時必須使用同步語句。比如,activity讀寫檔案時,需要用一個對象作為同步語句的内部鎖。
内部鎖對象
staticfinalobject[] sdatalock
=newobject[0];
有趣的事實:長度為零的數組要比普通對象更輕量化,是以用作内部鎖會是個好主意。
然後,每次讀寫檔案時用這個鎖建立同步語句。以下是把遊戲分數寫入檔案的同步語句示例:
synchronized(myactivity.sdatalock){
file datafile
=newfile(getfilesdir(),
top_scores);
randomaccessfile rafile
=newrandomaccessfile(datafile,"rw");
rafile.writeint(score);
log.e(tag,"unable
to write to file");
應該用同一個鎖同步讀取檔案的語句。
然後,在backupagenthelper内,必須覆寫onbackup()和onrestore()方法,用同一個内部鎖同步備份和恢複操作。比如,上例中myfilebackupagent需要以下方法:
publicvoid onbackup(parcelfiledescriptor
oldstate,backupdataoutput data,
// hold the lock while the filebackuphelper performs backup
super.onbackup(oldstate,
data, newstate);
// hold the lock while the filebackuphelper restores the file
super.onrestore(data, appversioncode,
newstate);
好了,所有要做的工作僅僅是在oncreate()方法内加入filebackuphelper,覆寫onbackup()和onrestore()并同步讀寫。
關于用filebackuphelper實作backupagenthelper的例子,請參閱例程備份和恢複中的filehelperexampleagent類。
在把資料儲存到雲存儲中去時,備份管理器會自動包含應用程式的版本号,版本号是在manifest檔案的android:versioncode
屬性中定義的。在調用備份代理恢複資料之前,備份管理器會查詢已安裝程式的android:versioncode,并與記錄在備份資料中的版本号相比較。如果備份資料的版本比裝置上的要新,則意味着使用者安裝了舊版本的程式。這時備份管理器将停止恢複操作,onrestore()方法也不會被調用,因為把資料恢複給舊版本的程式是沒有意義的。
用android:restoreanyversion屬性可以取代以上規則。此屬性用“true”或“false”标明是否在恢複時忽略資料集的版本,預設值是“false”。如果将其設為“true”,備份管理器将忽略android:versioncode
并且每次都會調用onrestore()方法。這時候可以在onrestore()裡人工檢查版本,并在版本沖突時采取必要的措施保證資料的相容性。
為了便于在恢複資料時對版本号進行判斷處理,onrestore()把備份資料的版本号作為appversioncode
參數和資料一起傳入方法。而用packageinfo.versioncode可以查詢目前應用程式的版本号,例如:
packageinfo info;
string name
=getpackagename();
info
=getpackagemanager().getpackageinfo(name,0);
}catch(namenotfoundexception nnfe){
=null;
int version;
if(info
!=null){
version
= info.versioncode;
然後,簡單比較一下packageinfo中的version和傳入onrestore()的appversioncode即可。
警告:請确認已經了解了android:restoreanyversion
設為“true”的後果。如果不是所有版本的程式都能在onrestore()時正确解析資料格式的差異,那麼儲存到裝置上的資料格式可能會與已安裝的版本不相容。
任何時候都可以通過調用datachanged()來發起備份請求。此方法通知備份管理器用備份代理來備份資料。然後,備份管理器将會适時調用備份代理的onbackup()方法。通常每次資料發生變化時都應該請求備份資料(比如使用者修改了需儲存的程式配置)。如果在備份管理器實際執行前連續調用了datachanged()很多次,代理僅會執行一次onbackup()。
注: 在程式開發過程中,可以用bmgr tool發起備份請求,備份将會立即執行。
在程式正常的生命周期内,應該不需要發起恢複資料的請求。在程式安裝完成時,系統會自動檢查備份資料并執行恢複操作。不過必要時,也可以通過調用requestrestore()來人工發起恢複資料的請求。這時,備份管理器會調用onrestore(),并把現有備份資料集作為資料傳入該方法。
注:在程式開發過程中,可以用bmgr tool發起恢複資料的請求。
一旦實作了備份代理,就可以用bmgr按以下步驟測試備份和恢複功能了:
1. 在合适的android系統鏡像上安裝應用程式
o 如果使用仿真器,須建立和使用android 2.2(api level 8)以上版本的avd。
o 如果使用硬體裝置,則此裝置必須運作android 2.2以上版本并内置android market功能。
2. 確定啟用備份功能
o 如果使用仿真器,可以在sdk tools/路徑下用以下指令啟用備份功能:
adb shell bmgr enable true
o 如果使用硬體裝置,則在系統settings, 選擇privacy,啟用back up my data 和automatic restore。
3. 運作程式并初始化一些資料。
如果程式已經正确地實作了備份代碼,那每次資料變化時都會請求備份。例如,每當使用者改變了一些資料,程式将調用datachanged(),這就往備份管理器的請求隊列裡加入了一個備份請求。出于測試的目的,也可以用以下bmgr指令發起一個請求:
adb shell bmgr backup
your.package.name
4. 執行備份操作:
adb shell bmgr run
這一步強迫備份管理器執行所有已入隊列的備份請求。
5. 解除安裝應用程式:
adb uninstall
6. 重新安裝應用程式。
如果備份代理成功運作,那第4步裡備份的資料将會被恢複。
補充
文章精選
[ibm]基于 android 資料備份恢複的一種實作