Android數據存儲---數據備份(Data Backup)(二)

在清單文件中聲明備份代理

這是最容易的一步,因此一旦決定了備份代理的類名稱,就可以在清單文件的<application>元素的android:backupAgent屬性中聲明它,例如:

<manifest ... >
    ...

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

        </activity>
    
</application>
</manifest>

另一個可能要使用的屬性是android:restoreAnyVersion。這個屬性需要一個布爾值,來指明是否要對恢復數據的應用程序版本與備份時的應用程序的版本進行比較(默認值是false,不比較)。

注意:備份服務和對應的API必須要運行API級別8或更高版本的設備上使用,因此還應該設置android:minSdkVersion屬性值爲“8”。

註冊Android備份服務

Google爲大多數運行Android2.2或更高版本的設備提供一個帶有Android備份服務的備份傳輸器。

應用程序爲了使用Android備份服務來執行備份操作,就必須給應用程序註冊這個服務,以便獲取一個備份服務鍵,然後再應用的清單文件中聲明這個備份服務鍵。

要獲取備份服務鍵,就要像Android的備份服務器註冊。在註冊時,你會獲得一個備份服務鍵,以及對應的用於應用程序清單文件的<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備份服務器註冊授權的備份服務鍵。

如果有多個應用程序需要備份服務,則每個應用都必須分別使用相應的包名來註冊一個鍵。

注意:由Android備份服務提供的備份傳輸器並保證在所有的支持備份的Android設備上都是有效的。某些設備可能使用一個不同的傳輸器來支持備份,而某些設備則可能完全不支持備份,並且應用程序沒有辦法來了解設備所支持的傳輸器是什麼。但是,如果應用程序自己實現了備份,那麼應用程序就應該始終包含一個Android的備份服務鍵,以便應用程序能夠在使用Android備份服務傳輸器的設備上執行備份操作。如果設備不使用Android備份服務,那麼帶有備份服務鍵的<meta-data>元素就會被忽略。

擴展BackupAgent

大多數應用程序不應該直接繼承BackupAgent類,但是應該繼承BackupAgentHelper類,它內置的helper類會自動的備份和恢復文件。但是,如果需要,還是可以直接繼承BackAgent類。

1. 數據格式的版本差異。例如,如果預料到要修改應用程序數據的格式,就可以構建一個備份代理,以便在執行恢復操作期間檢查應用程序的版本,並且如果在設備上運行的應用程序的版本與備份時應用的版本不同,它還可以執行一些必要的兼容性工作。

2. 代替備份整個文件。能夠指定具體要備份的那部分數據,以及每部分應該如何被恢復到設備上。(這也能幫助你管理不同的版本,因爲要讀寫數據的是唯一的實體,而不是整個文件。)

3. 備份數據庫中數據。如果在重新安裝程序時,想要恢復一個SQLite數據庫,就需要構建一個定製的BackupAgent類,讓這個類在備份操作期間來讀取適當的數據,然後創建適當的表,並插入恢復操作期間的數據。

如果不需要執行以上任何一種任務,並且想要備份來自SharedPreferences或內部存儲器中整個文件,就應該繼承BackupAgentHelper類。

要求實現的方法

在通過繼承BackupAgent類來創建一個備份代理時,必須實現下列回調方法:

onBackup()

在接到備份請求後,備份管理器會調用這個方法。在這個方法中,從設備上讀取數據,並且把數據傳遞給想要備份的備份管理器。

onRestore()

在執行數據恢復操作期間,備份管理器會調用這個方法。你可以請求一個恢復操作,但是通常在用戶重新安裝應用程序時,系統會自動執行數據恢復操作。當這個方法被調用時,備份管理器發送備份數據,以便在需要的時候再把這些數據恢復到設備上。

執行備份操作

在應用程序備份數據的時候,備份管理器要調用備份代理的onBackup()方法。這時必須把要備份的應用程序數據提供備份管理器,以便備份管理器能夠把數據保存到雲端。

只有備份管理器能夠調用備份代理的onBackup()方法,每次數據變化和想要執行備份操作時,必須通過調用dataChanaged()方法來請求備份操作。備份請求不會導致onBackup()方法的立即調用,相反,備份管理器會等待適當的時機,然後爲所有的請求備份的應用程序執行備份操作,直到最後一個備份被執行完成。

提示:在開發應用程序時,可以使用bmgr工具來立即啓動備份操作。

在備份管理調用onBackup()方法時,要傳遞三個參數:

oldState

由應用程序提供的一個打開的、只讀的指向最後一次備份狀態的ParcelFileDescriptor對象。這個對象不是來自雲端的備份數據,而是一個本地的最後一次調用onBackup()方法備份數據時的數據描述。因爲onBackup()方法不允許讀取雲端的既存的備份數據,所以能夠使用這個本地的表示來判定數據是否發生了改變。

data

一個BackupDataOutput對象,這個對象用於把備份數據發送給備份管理器。

newState

它是一個打開的,可讀可寫的ParcelFileDescriptor對象,它指向一個寫入了被髮送數據的數據描述的文件(這個描述可以像文件被最後一次修改的時間戳一樣簡單)。這個對象在下次備份管理器調用onBackup()方法時,被作爲oldState參數來返回。如果不把備份數據寫入newState對象,那麼在下次備份管理器調用onBackup()方法時,oldState就會指向一個空文件。

應該使用這些參數在onBackup()方法的實現中做以下事情:

1. 通過把oldState對象與當前數據的比較,來檢查當前數據是否發生了變化。如何讀取oldState對象中的數據,依賴於它是如何寫入的。記錄文件狀態的最容易的方法是用最後一次修改的時間戳。例如,以下代碼演示瞭如何讀取和比較oldState對象中的時間戳:

// Get the oldState input stream
FileInputStream instream =newFileInputStream(oldState.getFileDescriptor());
DataInputStreamin=newDataInputStream(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
}

如果沒有改變,則不需要備份。

2. 如果通過比較oldState對象,發現數據已經發生了改變,那麼就會把當前的數據寫入data參數,然後把它備份到雲端。

必須把每個數據塊作爲一個實例寫入到BackupDataOutput對象中,一個實體是一個被二進制數據填充的實體,它用一個唯一的字符串作爲鍵來標識。這樣備份的數據集在概念上是一個鍵-值對的集合。

要把一個實體添加到備份數據集,必須要做下列事情:

    A.調用writeEntityHeader()方法,給要備份的數據實體添加數據頭,包括標識數據實體的唯一鍵以及數據尺寸。

    B.調用writeEntityData()方法,把要備份的數據寫入數據實體,它要求傳入包含備份數據的字節緩存以及要寫入數據實體的字節數(這個數應該跟傳入writeEntityHeader()方法的數據尺寸相匹配)。

例如,以下代碼把數據寫入一個字節流,然後再把這個字節流寫入一個數據實體:

// Create buffer stream and data output stream for our data
ByteArrayOutputStream bufStream =newByteArrayOutputStream();
DataOutputStream outWriter =newDataOutputStream(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);

每個要備份的數據片段都要執行這個操作,

3. 無論是否執行了備份操作,都要把當前的數據描述寫入newStateParcelFileDescriptor)的對象中。備份管理器會把這個對象保留在本地,作爲當前備份數據的描述。以便在下次調用onBackup()方法時作爲oldState參數來返回,這樣就能夠判斷另一次備份是否是必須的。如果不把當前數據狀態寫入這個文件,那麼下次調用onBackup()方法時,oldState參數將會指向一個空文件。

以下代碼使用最後一次修改的時間戳,把當前數據的描述寫入了newState對象:

FileOutputStream outstream =newFileOutputStream(newState.getFileDescriptor());
DataOutputStreamout=newDataOutputStream(outstream);

long modified = mDataFile.lastModified();
out.writeLong(modified);

警告:如果應用程序數據被保存到一個文件,那麼在訪問這個文件時,要確保使用同步的語句,以便在應用程序的Activity在對這個文件進行寫入操作時,備份代理不會讀取這個文件。

執行恢復操作

在恢復應用程序數據的時候,備份管理器會調用備份代理的onRestore()方法。當調用這個方法時,備份管理器會發送備份數據,以便能夠把數據恢復到設備上。

只有備份管理器能夠調用onRestore()方法,在系統安裝應用程序並存在該應用程序備份的數據時,系統會自動調用這個方法。但是,也可以通過調用requestRestore()方法來請求應用程序的數據恢復操作。

注意:在開發應用程序時,使用bmqr工具也能夠請求數據恢復操作。

在備份管理器調用onRestore()方法時,它會傳遞三個參數:

data

一個BackupDataInput對象,它允許讀取備份數據。

appVersionCode

一個整數值,它是數據備份時的應用程序的android:versionCode清單屬性的值。可以使用這個值來檢查當前應用程序的版本,並判定數據格式是否兼容。

newState

一個打開的、可讀可寫的ParcelFileDescriptor對象,它指向一個寫入data參數所提供的最終備份狀態的文件。這個對象會作爲下次onBackup()方法被調用時的oldState參數。要記住的是,onBackup()回調中也必須寫入相同的newState對象,這樣做的目的是確保設備被恢復之後,即使是首次調用onBackup()方法,也能給onBackup()方法提供一個有效的oldState對象。

onRestore()方法的實現中,應該循環調用readNextHeader()方法來獲取數據中的所有備份實體,對應每個實體,要做以下事情:

1. 用getKey()方法獲取數據實體的鍵;

2. 把數據實體的鍵跟已知的鍵值列表(這個列表應該是在BackupAgent類中聲明的靜態字符串常量)進行比較。當跟鍵值列表中一個值匹配的時候,就會進入一個提取數據的語句,並把數據保存到設備上:

   A. 用getDataSize()方法獲取實體數據的大小,然後用這個尺寸來創建一個字節數組;

    B. 調用readEntityData()方法,並把字節數組傳遞給它,同時要給它指定開始讀取數據的偏移量和字節數;

    C. 現在可以從字節數組中讀取數據,並把數據寫入到設備上。

3. 讀取數據並把數據恢復到設備上之後,要像onBackup()方法執行期間一樣,把數據的狀態寫入到newState參數中。

例如:可以使用下面的代碼來恢復前面備份操作的例子中所備份的數據:

@Override
publicvoid onRestore(BackupDataInput data,int appVersionCode,
                      
ParcelFileDescriptor newState)throwsIOException{
    
// 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 =newbyte[dataSize];
            data
.readEntityData(dataBuf,0, dataSize);
            
ByteArrayInputStream baStream =newByteArrayInputStream(dataBuf);
            
DataInputStreamin=newDataInputStream(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 =newFileOutputStream(newState.getFileDescriptor());
    
DataOutputStreamout=newDataOutputStream(outstream);
    
out.writeUTF(mPlayerName);
    
out.writeInt(mPlayerScore);

在這個例子中,傳遞給onRestore()方法appVersionCode參數沒有使用。但是在用戶的應用程序版本向後發生了變化時(如,應用的版本號從1.5改變到1.0),如果選擇了要備份數據,就要可能要使用這個參數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章