android Data Backup(下)

執行恢復

恢復程序數據時,備份管理器將調用備份代理的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); 

 

          // 從備份數據中讀取playernamescore

          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 的實現必須要使用一個或多個backuphelperbackuphelper是一種專用組件,BackupAgentHelper 用它來對特定類型的數據執行備份和恢復操作。Android框架目前提供兩種helpers

·       SharedPreferencesBackupHelper用於備份SharedPreferences文件。

·       FileBackupHelper 用於備份來自內部存儲的文件。

BackupAgentHelper中可包含多個helper,但對於每種數據類型只需用到一個helper 。也就是說,即使存在多個SharedPreferences 文件,也只需要一個SharedPreferencesBackupHelper

對於每個要加入BackupAgentHelperhelper,必須在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來對給定文件執行備份和恢復操作。

注: SharedPreferences 是線程安全的,因此可以從備份代理和其它activity中安全地讀寫sharedpreferences文件。

 

備份其它文件

在實例化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屬性可以取代以上規則。此屬性用truefalse標明是否在恢復時忽略數據集的版本,默認值是false。如果將其設爲true,備份管理器將忽略android:versionCode 並且每次都會調用onRestore()方法。這時候可以在onRestore()里人工檢查版本,並在版本衝突時採取必要的措施保證數據的兼容性。

爲了便於在恢復數據時對版本號進行判斷處理,onRestore()把備份數據的版本號作爲appVersionCode 參數和數據一起傳入方法。而用PackageInfo.versionCode可以查詢當前應用程序的版本號,例如:

PackageInfo info; 

try { 

   String name = getPackageName(); 

   info = getPackageManager().getPackageInfo(name,0); 

} catch (NameNotFoundException nnfe) { 

   info = null; 

} 

 

int version; 

if (info != null) { 

   version = info.versionCode; 

}

然後,簡單比較一下PackageInfo 中的version 和傳入onRestore()appVersionCode 即可。

警告:請確認已經理解了android:restoreAnyVersion 設爲true的後果。如果不是所有版本的程序都能在onRestore()時正確解析數據格式的差異,那麼保存到設備上的數據格式可能會與已安裝的版本不兼容。

 

 

請求備份

任何時候都可以通過調用dataChanged()來發起備份請求。此方法通知備份管理器用備份代理來備份數據。然後,備份管理器將會適時調用備份代理的onBackup()方法。通常每次數據發生變化時都應該請求備份數據(比如用戶修改了需保存的程序配置)。如果在備份管理器實際執行前連續調用了dataChanged()很多次,代理僅會執行一次onBackup()

注: 在程序開發過程中,可以用bmgrtool發起備份請求,備份將會立即執行。

 

請求恢復

在程序正常的生命週期內,應該不需要發起恢復數據的請求。在程序安裝完成時,系統會自動檢查備份數據並執行恢復操作。不過必要時,也可以通過調用requestRestore()來人工發起恢復數據的請求。這時,備份管理器會調用onRestore(),並把現有備份數據集作爲數據傳入該方法。

注:在程序開發過程中,可以用bmgrtool發起恢復數據的請求。

 

 

測試備份代理

一旦實現了備份代理,就可以用bmgr按以下步驟測試備份和恢復功能了:

1. 在合適的Android系統鏡像上安裝應用程序

o    如果使用仿真器,須創建和使用Android2.2API 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. 執行備份操作:

adb shell bmgr run

這一步強迫備份管理器執行所有已入隊列的備份請求。

5. 卸載應用程序:

adbuninstall your.package.name

6. 重新安裝應用程序。

如果備份代理成功運行,那第4步裏備份的數據將會被恢復。

 

補充

         文章精選

                  [IBM]基於 android 數據備份恢復的一種實現

發佈了36 篇原創文章 · 獲贊 6 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章