Android Data Backup

The Basics

爲了備份應用數據,你須要實現一個 backup agent. 它用來爲BackupManager 提供你想要備份的數據,在你從新安裝應用時恢復數據。BackupManager 經過backup transport 處理和雲存儲相關的數據業務,經過你的backup agent 處理設備上的數據相關的業務。android

BackupAgent實現步驟:shell

  1. 在 manifest 文件中聲明 android:backupAgent 屬性。數據庫

  2. 經過backup服務註冊你的應用。谷歌爲大多數 Android 設備提供了 Android Backup Service,你須要經過這個服務註冊你的應用。其餘的雲服務也須要註冊。api

  3. 定義你的 backup agent:安全

    • 擴展 BackupAgent
      重寫 onBackup() onRestore()app

    • 擴展 BackupAgentHelper
      須要使用多個helper對象自動完成備份及恢復。
      會備份整個文件。dom

Declaring the Backup Agent in Your Manifest

application標籤中聲明android:backupAgent.ide

<manifest ... >
    ...
    <application android:label="MyApplication"
                 android:backupAgent="MyBackupAgent">
        <activity ... >
            ...
        </activity>
    </application>
</manifest>

android:restoreAnyVersion
默認值爲false, 設爲true 則會在該應用的任何版本恢復數據。工具

Registering for Android Backup Service

不一樣的設備對 backup 的支持不一樣。oop

<application android:label="MyApplication"
             android:backupAgent="MyBackupAgent">
    ...
    <meta-data android:name="com.google.android.backup.api_key"
        android:value="AEdPqrEAAAAIDaYEVgU6DJnyJdBmU7KLH3kszDXLv_4DIsEIyQ" />
</application>

Extending BackupAgent

通常狀況下,由於BackupAgentHelperBackupAgent更方便,因此儘量使用前者。
然而,若是有如下需求,仍是要經過BackupAgent來實現:

  • 對數據進行版本控制

  • 並不想備份整個數據文件

  • 備份數據庫中的數據

若是你想要備份SharedPreferences內部存儲中的整個文件,簡單地使用BackupAgentHelper便可。

Performing backup

oldState
上一次備份的狀態,主要是FileDescriptor

// Get the oldState input stream
FileInputStream instream = new FileInputStream(oldState.getFileDescriptor());
DataInputStream in = new DataInputStream(instream);

try {
    // Get the last modified timestamp from the state file and data file
    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
}

data
要備份的數據,經過byte字節流的方式存儲

// Create buffer stream and data output stream for our data
ByteArrayOutputStream bufStream = new ByteArrayOutputStream();
DataOutputStream outWriter = new DataOutputStream(bufStream);
// Write structured data
outWriter.writeUTF(mPlayerName);
outWriter.writeInt(mPlayerScore);
// Send the data to the Backup Manager via the BackupDataOutput
byte[] buffer = bufStream.toByteArray();
int len = buffer.length;
data.writeEntityHeader(TOPSCORE_BACKUP_KEY, len);
data.writeEntityData(buffer, len);

newState
本次備份的狀態,做爲下次備份時的oldState

Performing restore

data

用來恢復數據的 `BackupDataInput`.

appVersionCode

android:versionCode

newState
恢復後,須要把data裏的ParcelFileDescriptor 寫到newState中,它會如下次onBackupoldState被用到。

@Override
public void onRestore(BackupDataInput data, int appVersionCode,
                      ParcelFileDescriptor newState) throws IOException {
    // There should be only one entity, but the safest
    // way to consume it is using a while loop
    while (data.readNextHeader()) {
        String key = data.getKey();
        int dataSize = data.getDataSize();

        // If the key is ours (for saving top score). Note this key was used when
        // we wrote the backup entity header
        if (TOPSCORE_BACKUP_KEY.equals(key)) {
            // Create an input stream for the BackupDataInput
            byte[] dataBuf = new byte[dataSize];
            data.readEntityData(dataBuf, 0, dataSize);
            ByteArrayInputStream baStream = new ByteArrayInputStream(dataBuf);
            DataInputStream in = new DataInputStream(baStream);

            // Read the player name and score from the backup data
            mPlayerName = in.readUTF();
            mPlayerScore = in.readInt();

            // Record the score on the device (to a file or something)
            recordScore(mPlayerName, mPlayerScore);
        } else {
            // We don't know this entity key. Skip it. (Shouldn't happen.)
            data.skipEntityData();
        }
    }

    // Finally, write to the state blob (newState) that describes the restored data
    FileOutputStream outstream = new FileOutputStream(newState.getFileDescriptor());
    DataOutputStream out = new DataOutputStream(outstream);
    out.writeUTF(mPlayerName);
    out.writeInt(mPlayerScore);
}

Extending BackupAgentHelper

  • SharedPreferencesBackupHelper

  • FileBackupHelper

Backing up SharedPreferences

public class MyPrefsBackupAgent extends BackupAgentHelper {
    // The name of the SharedPreferences file
    static final String PREFS = "user_preferences";

    // A key to uniquely identify the set of backup data
    static final String PREFS_BACKUP_KEY = "prefs";

    // Allocate a helper and add it to the backup agent
    @Override
    public void onCreate() {
        SharedPreferencesBackupHelper helper =
                new SharedPreferencesBackupHelper(this, PREFS);
        addHelper(PREFS_BACKUP_KEY, helper);
    }
}

Note: SharedPreferences are threadsafe, so you can safely read and write the shared preferences file from your backup agent and other activities.

Backing up other files

public class MyFileBackupAgent extends BackupAgentHelper {
    // The name of the file
    static final String TOP_SCORES = "scores";
    static final String PLAYER_STATS = "stats";

    // A key to uniquely identify the set of backup data
    static final String FILES_BACKUP_KEY = "myfiles";

    // Allocate a helper and add it to the backup agent
    @Override
    public void onCreate() {
        FileBackupHelper helper = new FileBackupHelper(this,
                TOP_SCORES, PLAYER_STATS);
        addHelper(FILES_BACKUP_KEY, helper);
    }
}

非線程安全,須要手動加鎖

// Object for intrinsic lock
static final Object sDataLock = new Object();
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, "Unable to write to file");
}

@Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
          ParcelFileDescriptor newState) throws IOException {
    // Hold the lock while the FileBackupHelper performs backup
    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 the file
    synchronized (MyActivity.sDataLock) {
        super.onRestore(data, appVersionCode, newState);
    }
}

Checking the Restore Data Version

android:versionCode
BackupManager恢復數據前會檢查versionCode字段,若是待恢復的版本高於當前應用版本,BackupManager不會調用onRestore()來恢復數據。
android:restoreAnyVersion
設定trueorfalse指明你是否但願經過應用的版原本決定是否恢復數據。設爲true,那麼BackupManager將不會去檢查android:versionCode,而是調用你的onRestore()方法。
你能夠經過下面的方法來獲取appVersionCode.

PackageInfo info;
try {
    String name = getPackageName();
    info = getPackageManager().getPackageInfo(name,0);
} catch (NameNotFoundException nnfe) {
    info = null;
}

int version;
if (info != null) {
    version = info.versionCode;
}

Requesting Backup

  • 使用dataChanged()來請求備份,這個不會當即執行,會在將來某個合適的時間內執行

  • 開發時使用bmgr工具來隨時備份

Requesting Restore

  • 自動檢測備份數據

  • requestRestore()

  • bmgr工具

Testing Your Backup Agent

  1. 安裝應用

  2. 肯定備份功能可用

    • 使用模擬器
      adb shell bmgr enable true

    • If using a device, open the system Settings, select Backup & reset, then enable Back up my data and Automatic restore.

  3. 在用戶改變數據的地方調用dataChanged()
    而後執行:adb shell bmgr backup your.package.name

  1. 初始化備份操做

    adb shell bmgr run

  2. 卸載應用

    adb uninstall your.package.name

  3. 從新安裝

相關文章
相關標籤/搜索