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),如果选择了要备份数据,就要可能要使用这个参数。

 

你可能感兴趣的:(android,Stream,数据备份,application,存储,buffer)