2.14 備份Android應用程序數據
Pratik Rupwal
2.14.1 問題
當用戶恢復出廠設置或者改用新的Android設備時,應用程序丟失存儲數據或者應用程序設置。
2.14.2 解決方案
Android的Backup Manager(備份管理器)能夠在應用程序重新安裝時自動恢復備份數據或者應用程序設置。
2.14.3 討論
Android的備份管理器本質上以兩種模式運行——備份和恢復。在備份操作期間,備份管理器(BackuManager類)詢問應用程序所要備份的數據,并將其放入一個備份傳輸中,備份傳輸負責將數據發送到基于云的存儲中。在恢復操作期間,備份管理器從備份傳輸中讀取備份數據,并將其返回給應用程序,以便將數據恢復到設備上。應用程序可以請求恢復,但是在應用程序安裝且與用戶關聯的備份數據存在時,Android并不一定執行恢復操作。恢復備份數據主要發生在用戶重置設備或者升級到新設備,并且重新安裝過去安裝的應用程序時。
例2-19展示了為應用程序實現備份管理器以保存應用程序當前狀態的方法。
以下是這一過程各個步驟的簡單描述:
1 . 在Eclipse中創建BackupManagerExample項目。
2 . 打開layout/backup_restore.xml文件,并插入例2-19中的代碼。
3 . 打開values/string.xml文件并插入例2-20中的代碼。
4 . 清單文件看上去將類似于例2-21。
5 . 例2-22中的代碼完成了應用程序備份管理器的實現。
例2-19:備份/恢復布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="wrap_content"><ScrollViewandroid:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent"android:layout_weight="1"><LinearLayoutandroid:orientation="vertical"android:layout_width="match_parent"android:layout_height="wrap_content"><TextView android:text="@string/filling_text"android:textSize="20dp"android:layout_marginTop="20dp"android:layout_marginBottom="10dp"android:layout_width="match_parent"android:layout_height="wrap_content"/><RadioGroup android:id="@+id/filling_group"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginLeft="20dp"android:orientation="vertical"><RadioButton android:id="@+id/bacon"android:text="@string/bacon_label"/><RadioButton android:id="@+id/pastrami"android:text="@string/pastrami_label"/><RadioButton android:id="@+id/hummus"android:text="@string/hummus_label"/></RadioGroup><TextView android:text="@string/extras_text"android:textSize="20dp"android:layout_marginTop="20dp"android:layout_marginBottom="10dp"android:layout_width="match_parent"android:layout_height="wrap_content"/><CheckBox android:id="@+id/mayo"android:text="@string/mayo_text"android:layout_marginLeft="20dp"android:layout_width="match_parent"android:layout_height="wrap_content"/><CheckBox android:id="@+id/tomato"android:text="@string/tomato_text"android:layout_marginLeft="20dp"android:layout_width="match_parent"android:layout_height="wrap_content"/></LinearLayout></ScrollView>
</LinearLayout>
例2-20:示例使用的字符串
<resources><string name="hello">Hello World, BackupManager!</string><string name="app_name">BackupManager</string><string name="filling_text">Choose Settings for your application:</string><string name="bacon_label">Sound On</string><string name="pastrami_label">Vibration On</string><string name="hummus_label">Backlight On</string><string name="extras_text">Extras:</string><string name="mayo_text">Use Orientation?</string><string name="tomato_text">Use Camera?</string>
</resources>
例2-21:AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.sym.backupmanager"android:versionCode="1"android:versionName="1.0"><uses-sdk android:minSdkVersion="9" /><application android:label="Backup/Restore" android:icon="@drawable/icon"android:backupAgent="ExampleAgent"> <!-- Here you specify the backup agent--><!--Some backup transports may require API keys or other metadata--><meta-data android:name="com.google.android.backup.api_key"android:value="INSERT YOUR API KEY HERE" /><activity android:name=".BackupManagerExample"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity> </application>
</manifest>
例2-22:備份/恢復活動
package com.sym.backupmanager;
import android.app.Activity;
import android.app.backup.BackupManager;
import android.os.Bundle;
import android.util.Log;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.RadioGroup;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
public class BackupManagerExample extends Activity {static final String TAG = "BRActivity";static final Object[] sDataLock = new Object[0];static final String DATA_FILE_NAME = "saved_data";RadioGroup mFillingGroup;CheckBox mAddMayoCheckbox;CheckBox mAddTomatoCheckbox;File mDataFile;BackupManager mBackupManager;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.backup_restore);mFillingGroup = (RadioGroup) findViewById(R.id.filling_group);mAddMayoCheckbox = (CheckBox) findViewById(R.id.mayo);mAddTomatoCheckbox = (CheckBox) findViewById(R.id.tomato);mDataFile = new File(getFilesDir(), BackupManagerExample.DATA_FILE_NAME);mBackupManager = new BackupManager(this);populateUI();}void populateUI() {RandomAccessFile file;int whichFilling = R.id.pastrami;boolean addMayo = false;boolean addTomato = false;synchronized (BackupManagerExample.sDataLock) {boolean exists = mDataFile.exists();try {file = new RandomAccessFile(mDataFile, "rw");if (exists) {Log.v(TAG, "datafile exists");whichFilling = file.readInt();addMayo = file.readBoolean();addTomato = file.readBoolean();Log.v(TAG, " mayo=" + addMayo+ " tomato=" + addTomato+ " filling=" + whichFilling);} else {Log.v(TAG, "creating default datafile");writeDataToFileLocked(file,addMayo, addTomato, whichFilling);mBackupManager.dataChanged();}} catch (IOException ioe) {// 在這里進行錯誤處理!}}mFillingGroup.check(whichFilling);mAddMayoCheckbox.setChecked(addMayo);mAddTomatoCheckbox.setChecked(addTomato);mFillingGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {public void onCheckedChanged(RadioGroup group,int checkedId) {Log.v(TAG, "New radio item selected: " + checkedId);recordNewUIState();}});CompoundButton.OnCheckedChangeListener checkListener= new CompoundButton.OnCheckedChangeListener() {public void onCheckedChanged(CompoundButton buttonView,boolean isChecked) {Log.v(TAG, "Checkbox toggled: " + buttonView);recordNewUIState();}};mAddMayoCheckbox.setOnCheckedChangeListener(checkListener);mAddTomatoCheckbox.setOnCheckedChangeListener(checkListener);}void writeDataToFileLocked(RandomAccessFile file,boolean addMayo, boolean addTomato, int whichFilling)throws IOException {file.setLength(0L);file.writeInt(whichFilling);file.writeBoolean(addMayo);file.writeBoolean(addTomato);Log.v(TAG, "NEW STATE: mayo=" + addMayo+ " tomato=" + addTomato+ " filling=" + whichFilling);}void recordNewUIState() {boolean addMayo = mAddMayoCheckbox.isChecked();boolean addTomato = mAddTomatoCheckbox.isChecked();int whichFilling = mFillingGroup.getCheckedRadioButtonId();try {synchronized (BackupManagerExample.sDataLock) {RandomAccessFile file = new RandomAccessFile(mDataFile, "rw");writeDataToFileLocked(file, addMayo, addTomato, whichFilling);}} catch (IOException e) {Log.e(TAG, "Unable to record new UI state");}mBackupManager.dataChanged();}
}
數據備份不能保證在所有Android設備上可用。但是,如果設備沒有提供備份傳輸,應用程序并不會受到不利影響。如果你相信用戶將從應用程序的數據備份中獲益,就可以按照這個文檔中描述的步驟實現、測試這一功能,然后發布應用程序,而不考慮設備是否實際執行備份。當應用程序在不提供備份傳輸的設備上運行時,它將正常運作,但是不會接受來自備份管理器的回調來備份數據。
盡管你無法知道當前傳輸手段是什么,但是始終可以確信,備份數據不會被設備上的其他應用程序讀取。只有備份管理器和備份傳輸有權訪問備份操作中所提供的數據。
警告: 因為云存儲和傳輸服務在不同的設備上可能有差別,Android不能保證備份中的數據安全。在使用備份存儲敏感數據(如用戶名和密碼)時,應該始終保持警惕。
測試你的備份代理
實現備份代理之后,可以使用bmgr命令,按照如下步驟測試備份/恢復功能:
1 . 在合適的Android系統映像上安裝你的應用程序。如果使用模擬器,創建和使用帶有Android 2.2(API Level 8)的AVD。如果使用真實設備,設備必須運行Android 2.2或更高版本并內建Android Market。
2 . 確保備份功能啟用。如果使用的是模擬器,可以從SDK tools/路徑用如下命令啟用備份功能:
adb shell bmgr enable true
如果使用的是設備,打開系統設置,選擇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 your.package.name
6 . 重新安裝應用程序。
如果備份代理成功,第4步中初始化的所有數據將被恢復。