執行恢復
恢復程序數據時,備份管理器將調用備份代理的onRestore()方法。調用此方法時,備份管理器會把備份的數據傳入,以供恢復到設備中去。
只有備份服務器能夠調用onRestore(),在系統安裝應用程序並且發現有備份數據存在時,調用會自動發生。不過,也可以通過調用requestRestore()來發起恢復數據的請求(詳情參閱請求恢復)。
注: 在開發應用程序的過程中,可以用bmgr工具發起恢復數據的請求。
當備份管理器調用onRestore() 方法時,傳入以下三個參數:
data
BackupDataInput對象,用以讀取備份數據。
appVersionCode
整數,表示備份數據時應用程序manifest中的android:versionCode屬性。可以用於覈對當前程序版本並確定數據格式的兼容性。關於利用此版本號來處理不同版本恢復數據的詳細情況,請參閱下文檢查恢復數據的版本。
newState
已打開的,可讀寫的文件描述符ParcelFileDescriptor,指向一個文件,這裏必須寫入最後一次提交data數據的備份狀態。本對象在下次調用onBackup()時作爲oldState 返回。回想一下,onBackup()方法也必須寫入newState 對象——這裏也同樣要這麼做。這樣即使設備重置後第一次調用onBackup(),也能確保有可用的oldState對象能傳給onBackup()方法。
在實現onRestore()時,應該對data 調用readNextHeader(),以遍歷數據集裏所有的entity。對其中每個entity須進行以下操作:
1. 用getKey()獲取entity的鍵值。
2. 將此entity鍵值和已知鍵值清單進行比較,這個清單應該已經在BackupAgent繼承類中作爲字符串常量(staticfinal
string)進行定義。一旦鍵值匹配其中一個鍵,就執行讀取entity數據並保存到設備的語句:
1. 用getDataSize()讀取entity數據大小並據其創建字節數組。
2. 調用readEntityData() ,傳入字節數組作爲獲取數據的緩衝區,並指定起始位置和讀取字節數。
3. 字節數組將被填入數據,按需讀取數據並寫入設備即可。
3. 把數據讀出並寫回設備以後,和上面onBackup()過程類似,把數據的狀態寫入newState 參數。
下面是把前一節例子中所備份的數據進行恢復的示例:
@Override
public void onRestore(BackupDataInput data, int appVersionCode,
ParcelFileDescriptor newState) throws IOException {
// 應該是隻有一個entity,
// 但最安全的方法還是用循環來處理
while (data.readNextHeader()) {
String key = data.getKey();
int dataSize = data.getDataSize();
// 如果鍵值是所需的(保存TopScore),注意這個鍵值是用於
// 寫入備份entityheader
if (TOPSCORE_BACKUP_KEY.equals(key)) {
// 爲BackupDataInput創建輸入流
byte[] dataBuf = new byte[dataSize];
data.readEntityData(dataBuf, 0, dataSize);
ByteArrayInputStream baStream = new ByteArrayInputStream(dataBuf);
DataInputStream in = new DataInputStream(baStream);
// 從備份數據中讀取playername和score
mPlayerName = in.readUTF();
mPlayerScore = in.readInt();
//Record the score on the device (to a file orsomething)
recordScore(mPlayerName, mPlayerScore);
} else {
// 不知道這個entity鍵值,跳過,(這本不該發生)
data.skipEntityData();
}
}
//Finally, write to the state blob (newState) that describes therestored
data
FileOutputStream outstream = new FileOutputStream(newState.getFileDescriptor());
DataOutputStream out = new DataOutputStream(outstream);
out.writeUTF(mPlayerName);
out.writeInt(mPlayerScore);
}
在以上例子中,傳給onRestore()的appVersionCode 參數沒有被用到。假如用戶程序的版本已經降級(比如從1.5降到1.0),可能就會用此參數來選擇備份數據。更多信息請參閱檢查恢復數據的版本。
關於BackupAgent的完整例子,請參閱例程備份和恢復中的ExampleAgent類。
繼承BackupAgentHelper
如果要備份整個文件(來自SharedPreferences或內部存儲),應該用BackupAgentHelper創建備份代理來實現。因爲不必實現onBackup()和onRestore()了,用BackupAgentHelper 創建備份代理所需的代碼量將遠遠少於BackupAgent。
BackupAgentHelper 的實現必須要使用一個或多個backuphelper。backuphelper是一種專用組件,BackupAgentHelper 用它來對特定類型的數據執行備份和恢復操作。Android框架目前提供兩種helpers:
· SharedPreferencesBackupHelper用於備份SharedPreferences文件。
· FileBackupHelper 用於備份來自內部存儲的文件。
在BackupAgentHelper中可包含多個helper,但對於每種數據類型只需用到一個helper 。也就是說,即使存在多個SharedPreferences 文件,也只需要一個SharedPreferencesBackupHelper。
對於每個要加入BackupAgentHelper的helper,必須在onCreate() 中執行以下步驟:
1. 實例化所需的helper。在其構造方法裏必須指定需備份的文件。
2. 調用addHelper() 把helper加入BackupAgentHelper。
下一節描述瞭如何使用每種helper創建備份代理。
備份SharedPreferences
實例化SharedPreferencesBackupHelper時,必須包括一個或多個SharedPreferences 文件。
例如,假設需備份的SharedPreferences文件名爲“user_preferences”,完整的使用BackupAgentHelper的備份代理代碼類似如下:
public class MyPrefsBackupAgent extends BackupAgentHelper {
//SharedPreferences 文件名
static final String PREFS = "user_preferences";
// 唯一標識備份數據的鍵值
static final String PREFS_BACKUP_KEY = "prefs";
// 申請helper並加入備份代理
@Override
public void onCreate() {
SharedPreferencesBackupHelper helper = new SharedPreferencesBackupHelper(this, PREFS);
addHelper(PREFS_BACKUP_KEY, helper);
}
}
好,這就是一個備份代理的完整實現。SharedPreferencesBackupHelper內含了備份和恢復SharedPreferences文件的所有代碼。
當備份管理器調用onBackup() 和onRestore()時,BackupAgentHelper 調用helper來對給定文件執行備份和恢復操作。
備份其它文件
在實例化FileBackupHelper時,必須包含一個或多個保存於程序內部存儲中的文件名稱。(路徑的描述方式類似getFilesDir(),並且作爲openFileOutput() 寫入文件的路徑。)
比如,需要備份兩個名爲“scores”和“stats”的文件,備份代理使用BackupAgentHelper 示例如下:
public class MyFileBackupAgent extends BackupAgentHelper {
//SharedPreferences文件的名稱
static final String TOP_SCORES = "scores";
static final String PLAYER_STATS = "stats";
// 唯一標識備份數據集的鍵值
static final String FILES_BACKUP_KEY = "myfiles";
// 申請helper並加入備份代理
void onCreate() {
FileBackupHelper helper = new FileBackupHelper(this, TOP_SCORES, PLAYER_STATS);
addHelper(FILES_BACKUP_KEY, helper);
}
}
FileBackupHelper 包含了備份和恢復存於內部存儲的文件所需的全部代碼。
但是,讀寫內部存儲文件不是線程安全的。要確保activity操作文件的時候備份代理不會去讀寫文件,每次讀寫文件時必須使用同步語句。比如,Activity讀寫文件時,需要用一個對象作爲同步語句的內部鎖。
// 內部鎖對象
static final Object[] sDataLock = new Object[0];
有趣的事實:長度爲零的數組要比普通對象更輕量化,因此用作內部鎖會是個好主意。
然後,每次讀寫文件時用這個鎖創建同步語句。以下是把遊戲分數寫入文件的同步語句示例:
try {
synchronized (MyActivity.sDataLock) {
File dataFile = new File(getFilesDir(), TOP_SCORES);
RandomAccessFile raFile = new RandomAccessFile(dataFile, "rw");
raFile.writeInt(score);
}
} catch (IOException e) {
Log.e(TAG, "Unableto
write to file");
}
應該用同一個鎖同步讀取文件的語句。
然後,在BackupAgentHelper內,必須覆蓋onBackup()和onRestore()方法,用同一個內部鎖同步備份和恢復操作。比如,上例中MyFileBackupAgent需要以下方法:
@Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) throws IOException {
//Hold the lock while the FileBackupHelper performsbackup
synchronized (MyActivity.sDataLock) {
super.onBackup(oldState, data, newState);
}
}
@Override
public void onRestore(BackupDataInput data, int appVersionCode,
ParcelFileDescriptor newState) throws IOException {
//Hold the lock while the FileBackupHelper restores thefile
synchronized (MyActivity.sDataLock) {
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 中的version 和傳入onRestore()的appVersionCode 即可。
請求備份
任何時候都可以通過調用dataChanged()來發起備份請求。此方法通知備份管理器用備份代理來備份數據。然後,備份管理器將會適時調用備份代理的onBackup()方法。通常每次數據發生變化時都應該請求備份數據(比如用戶修改了需保存的程序配置)。如果在備份管理器實際執行前連續調用了dataChanged()很多次,代理僅會執行一次onBackup()。
請求恢復
在程序正常的生命週期內,應該不需要發起恢復數據的請求。在程序安裝完成時,系統會自動檢查備份數據並執行恢復操作。不過必要時,也可以通過調用requestRestore()來人工發起恢復數據的請求。這時,備份管理器會調用onRestore(),並把現有備份數據集作爲數據傳入該方法。
測試備份代理
一旦實現了備份代理,就可以用bmgr按以下步驟測試備份和恢復功能了:
1. 在合適的Android系統鏡像上安裝應用程序
o 如果使用仿真器,須創建和使用Android2.2(API
Level8)以上版本的AVD。
o 如果使用硬件設備,則此設備必須運行Android2.2以上版本並內置AndroidMarket功能。
2. 確保啓用備份功能
o 如果使用仿真器,可以在SDK tools/路徑下用以下命令啓用備份功能:
adb shell bmgr enable true
o 如果使用硬件設備,則在系統Settings, 選擇Privacy,啓用Backup
my data 和Automaticrestore。
3. 運行程序並初始化一些數據。
如果程序已經正確地實現了備份代碼,那每次數據變化時都會請求備份。例如,每當用戶改變了一些數據,程序將調用dataChanged(),這就往備份管理器的請求隊列里加入了一個備份請求。出於測試的目的,也可以用以下bmgr命令發起一個請求:
adb shell bmgrbackup your.package.name
4. 執行備份操作:
這一步強迫備份管理器執行所有已入隊列的備份請求。
5. 卸載應用程序:
adbuninstall your.package.name
6. 重新安裝應用程序。
如果備份代理成功運行,那第4步裏備份的數據將會被恢復。
補充
文章精選
[IBM]基於 android 數據備份恢復的一種實現