HugeAgentpublic class HugeAgent extends android.app.backup.BackupAgent This is the backup/restore agent class for the BackupRestore sample
application. This particular agent illustrates using the backup and
restore APIs directly, without taking advantage of any helper classes. |
Fields Summary |
---|
static final int | AGENT_VERSIONWe put a simple version number into the state files so that we can
tell properly how to read "old" versions if at some point we want
to change what data we back up and how we store the state blob. | static final String | APP_DATA_KEYPick an arbitrary string to use as the "key" under which the
data is backed up. This key identifies different data records
within this one application's data set. Since we only maintain
one piece of data we don't need to distinguish, so we just pick
some arbitrary tag to use. | static final String | HUGE_DATA_KEY | boolean | mAddMayoThe app's current data, read from the live disk file | boolean | mAddTomato | int | mFilling | File | mDataFileThe location of the application's persistent data file |
Methods Summary |
---|
boolean | compareStateFile(android.os.ParcelFileDescriptor oldState)Helper routine - read a previous state file and decide whether to
perform a backup based on its contents.
FileInputStream instream = new FileInputStream(oldState.getFileDescriptor());
DataInputStream in = new DataInputStream(instream);
try {
int stateVersion = in.readInt();
if (stateVersion > AGENT_VERSION) {
// Whoops; the last version of the app that backed up
// data on this device was <em>newer</em> than the current
// version -- the user has downgraded. That's problematic.
// In this implementation, we recover by simply rewriting
// the backup.
return true;
}
// The state data we store is just a mirror of the app's data;
// read it from the state file then return 'true' if any of
// it differs from the current data.
int lastFilling = in.readInt();
boolean lastMayo = in.readBoolean();
boolean lastTomato = in.readBoolean();
return (lastFilling != mFilling)
|| (lastTomato != mAddTomato)
|| (lastMayo != mAddMayo);
} catch (IOException e) {
// If something went wrong reading the state file, be safe
// and back up the data again.
return true;
}
| public void | onBackup(android.os.ParcelFileDescriptor oldState, android.app.backup.BackupDataOutput data, android.os.ParcelFileDescriptor newState)The set of data backed up by this application is very small: just
two booleans and an integer. With such a simple dataset, it's
easiest to simply store a copy of the backed-up data as the state
blob describing the last dataset backed up. The state file
contents can be anything; it is private to the agent class, and
is never stored off-device.
One thing that an application may wish to do is tag the state
blob contents with a version number. This is so that if the
application is upgraded, the next time it attempts to do a backup,
it can detect that the last backup operation was performed by an
older version of the agent, and might therefore require different
handling.
// First, get the current data from the application's file. This
// may throw an IOException, but in that case something has gone
// badly wrong with the app's data on disk, and we do not want
// to back up garbage data. If we just let the exception go, the
// Backup Manager will handle it and simply skip the current
// backup operation.
synchronized (HugeBackupActivity.sDataLock) {
RandomAccessFile file = new RandomAccessFile(mDataFile, "r");
mFilling = file.readInt();
mAddMayo = file.readBoolean();
mAddTomato = file.readBoolean();
}
// If the new state file descriptor is null, this is the first time
// a backup is being performed, so we know we have to write the
// data. If there <em>is</em> a previous state blob, we want to
// double check whether the current data is actually different from
// our last backup, so that we can avoid transmitting redundant
// data to the storage backend.
boolean doBackup = (oldState == null);
if (!doBackup) {
doBackup = compareStateFile(oldState);
}
// If we decided that we do in fact need to write our dataset, go
// ahead and do that. The way this agent backs up the data is to
// flatten it into a single buffer, then write that to the backup
// transport under the single key string.
if (doBackup) {
ByteArrayOutputStream bufStream = new ByteArrayOutputStream();
// We use a DataOutputStream to write structured data into
// the buffering stream
DataOutputStream outWriter = new DataOutputStream(bufStream);
outWriter.writeInt(mFilling);
outWriter.writeBoolean(mAddMayo);
outWriter.writeBoolean(mAddTomato);
// Okay, we've flattened the data for transmission. Pull it
// out of the buffering stream object and send it off.
byte[] buffer = bufStream.toByteArray();
int len = buffer.length;
data.writeEntityHeader(APP_DATA_KEY, len);
data.writeEntityData(buffer, len);
// ***** pathological behavior *****
// Now, in order to incur deliberate too-much-data failures,
// try to back up 20 MB of data besides what we already pushed.
final int MEGABYTE = 1024*1024;
final int NUM_MEGS = 20;
buffer = new byte[MEGABYTE];
data.writeEntityHeader(HUGE_DATA_KEY, NUM_MEGS * MEGABYTE);
for (int i = 0; i < NUM_MEGS; i++) {
data.writeEntityData(buffer, MEGABYTE);
}
}
// Finally, in all cases, we need to write the new state blob
writeStateFile(newState);
| public void | onCreate()For convenience, we set up the File object for the app's data on creation
mDataFile = new File(getFilesDir(), HugeBackupActivity.DATA_FILE_NAME);
| public void | onRestore(android.app.backup.BackupDataInput data, int appVersionCode, android.os.ParcelFileDescriptor newState)This application does not do any "live" restores of its own data,
so the only time a restore will happen is when the application is
installed. This means that the activity itself is not going to
be running while we change its data out from under it. That, in
turn, means that there is no need to send out any sort of notification
of the new data: we only need to read the data from the stream
provided here, build the application's new data file, and then
write our new backup state blob that will be consulted at the next
backup operation.
We don't bother checking the versionCode of the app who originated
the data because we have never revised the backup data format. If
we had, the 'appVersionCode' parameter would tell us how we should
interpret the data we're about to read.
// We should only see one entity in the data stream, but the safest
// way to consume it is using a while() loop
while (data.readNextHeader()) {
String key = data.getKey();
int dataSize = data.getDataSize();
if (APP_DATA_KEY.equals(key)) {
// It's our saved data, a flattened chunk of data all in
// one buffer. Use some handy structured I/O classes to
// extract it.
byte[] dataBuf = new byte[dataSize];
data.readEntityData(dataBuf, 0, dataSize);
ByteArrayInputStream baStream = new ByteArrayInputStream(dataBuf);
DataInputStream in = new DataInputStream(baStream);
mFilling = in.readInt();
mAddMayo = in.readBoolean();
mAddTomato = in.readBoolean();
// Now we are ready to construct the app's data file based
// on the data we are restoring from.
synchronized (HugeBackupActivity.sDataLock) {
RandomAccessFile file = new RandomAccessFile(mDataFile, "rw");
file.setLength(0L);
file.writeInt(mFilling);
file.writeBoolean(mAddMayo);
file.writeBoolean(mAddTomato);
}
} else {
// Curious! This entity is data under a key we do not
// understand how to process. Just skip it.
data.skipEntityData();
}
}
// The last thing to do is write the state blob that describes the
// app's data as restored from backup.
writeStateFile(newState);
| void | writeStateFile(android.os.ParcelFileDescriptor stateFile)Write out the new state file: the version number, followed by the
three bits of data as we sent them off to the backup transport.
FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor());
DataOutputStream out = new DataOutputStream(outstream);
out.writeInt(AGENT_VERSION);
out.writeInt(mFilling);
out.writeBoolean(mAddMayo);
out.writeBoolean(mAddTomato);
|
|