Backing up data to the cloud

Often, an app will need to backup it's data, especially if the data took time, effort or money to collect. The Android Backup Service provides a means of preserving data that is more complex than simple shared preferences or files.

Getting ready...

In order to work with the backup API, we have to have registered our app package name with the Android Backup Service.

We do this on http://developer.android.com/google/backup/signup.html.

How to do it...

In this recipe, we will save a value to a file and read from a file. This can be a far more comprehensive set of actions on a larger dataset, but not necessary, for example:

  1. Our app is not going to do much with the data, so we can save our single value to the filesystem as a basic write operation:
    File.WriteAllText(dataPath, clickCount.ToString());
  2. Additionally, to read the data on the app start, we will do a basic read:
    if (File.Exists(dataPath)) {
      clickCount = int.Parse(File.ReadAllText(dataPath));
    }

Now that we have the data moving in our app, we need to hook up the backup. Although this example uses a simple file stored locally, we can back up data in a database using an algorithm to determine whether a backup needs to be done. Let's take a look at the following steps:

  1. Just as we did when we used backup helpers (refer to the previous recipe), we need to register the agent with the application using the backup key:
    [assembly: MetaData(
      "com.google.android.backup.api_key", 
      Value = "AndroidBackupServiceKey")]
  2. In the case of our agent, we inherit it from the base BackupAgent instance:
    public class BackupHelper : BackupAgent {
      public override void OnBackup(
        ParcelFileDescriptor oldState,
        BackupDataOutput data,
        ParcelFileDescriptor newState) {
          // backup logic
        }
      public override void OnRestore(
        BackupDataInput data,
        int appVersionCode,
        ParcelFileDescriptor newState) {
          // restore logic
        }
    }
  3. We now need to create the logic that actually creates the backup logic. We do this in the OnBackup() method in four main steps. First, we read the state or metadata associated with the last backup, if any:
    var stateDate = DateTime.MinValue;
    if (oldState != null) {
      using (var descr = oldState.FileDescriptor)
      using (var file = new FileInputStream(descr))
      using (var invoker = new InputStreamInvoker(file))
      using (var stream = new DataInputStream(invoker)) {
        stateDate = DateTime.FromBinary(stream.ReadLong());
      }
    }
  4. Next, we get the state of the data that we need to backup, in our case, the file's last write date:
    var fileDate = DateTime.MinValue;
    if (File.Exists(dataPath)) {
      fileDate = File.GetLastWriteTimeUtc(dataPath);
    }
    else {
      stateDate = DateTime.MinValue;
    }
  5. Now, we can compare the states, and if they differ, we perform the backup. Here, we just back up the entire contents of the file, but we can back up any data or subset of data:
    if (stateDate != fileDate) {
      var buffer = File.ReadAllBytes(dataPath);
      data.WriteEntityHeader("DataHeader", buffer.Length);
      data.WriteEntityData(buffer, buffer.Length);
    }
  6. Whether or not we do a backup, we now have to let the agent know what state we are in, and in our case, just the last time the file was modified:
    using (var descr = newState.FileDescriptor)
    using (var file = new FileOutputStream(descr))
    using (var invoker = new OutputStreamInvoker(file))
    using (var stream = new DataOutputStream(invoker)) {
      stream.WriteLong(fileDate.ToBinary());
    }
  7. Now we need to work on the restore logic in the OnRestore() method. We first have to go through all the bits of data that were backed up until we find the header we are looking for, that is, what we know our is app saved:
    while (data.ReadNextHeader()) {
      if (data.Key == "DataHeader") {
        var buffer = new byte[data.DataSize];
        data.ReadEntityData(buffer, 0, data.DataSize);
        File.WriteAllBytes(dataPath, buffer);
        break;
      }
      else {
        data.SkipEntityData();
      }
    }
  8. Lastly, we have to let the agent know about the current state of the backup, and again in our case, just the time of the last modification of the file:
    if (File.Exists(dataPath)) {
      using (var descr = newState.FileDescriptor)
      using (var file = new FileOutputStream(descr))
      using (var invoker = new OutputStreamInvoker(file))
      using (var stream = new DataOutputStream(invoker)) {
        var fileDate = File.GetLastWriteTimeUtc(dataPath);
        stream.WriteLong(fileDate.ToBinary());
      }
    }

How it works...

We can easily back up shared preferences or entire files, but sometimes, we need to have more flexibility or control. To do this, we can create an instance of the base BackupAgent instance.

There are several advantages of using the base agent directly as opposed to the helpers. The most notable advantage is the ability to version data and appropriately upgrade or downgrade the data. There is also the powerful ability to back up and restore partial data, such as a section of a larger file. We can also back up specific tables in a database. This will allow us to better version the data as well as reduce the size by excluding static data.

There are two stages to any backup process: backup and restore. The backup stage consists of four main steps:

  1. The first step is to read the state or metadata associated with the last backup from the oldState parameter. This state is used to identify the backed up data but is not the data itself; it can be anything, even a simple, last-modified timestamp of the data. If the state is null, then no backup or restore has been performed and we should take a backup.
  2. We now need to be able to make a decision to backup, so we need to obtain the state of the data itself. If we are backing up data only if it has changed, we need to obtain the timestamp of the last change. If we are backing up a database, we may use the last login date.
  3. Now that we have both the state of the backup and the state of the data, we can determine whether a backup is needed. To perform a backup, we populate the BackupDataOutput object with a set of key-value entities. We first write a header using the WriteEntityHeader() method, and then the actual data using the WriteEntityData() method. If we are backing up a database table, we can read the contents into an XML document and then write that.
  4. Finally, we have to let Android know the state of the backup in the newState parameter. Even if nothing was backed up, we can still write a value representing the fact that nothing was done, that is, the original value. This state is passed in as the oldState parameter the next time a backup is run. If we are backing up a table, we can write the last login date.

Performing a restore is a bit easier as we only have to execute two steps:

  1. We have to first go through the data in the BackupDataInput object and work with the keys we recognize. We can loop through the headers using the ReadNextHeader() method, and if we are working with different app versions, we may choose to ignore some using the SkipEntityData() method. If we want to read the data, we use the ReadEntityData() method.
  2. After we have restored the data, we now update the state of the data by writing to the newState parameter. If we are restoring a section of a file, we can use the timestamp. This state is passed in as the oldState parameter when a backup is run.

The OnBackup() method is called when the backup manager decides that it is time to run a backup. We can request a backup using the backup manager, but cannot control when it is run. The OnRestore() method is called by the backup manager as well, but is also automatically called when the app is installed. Like backups, we can request a restore but cannot directly control when it happens.

There's more...

During a restore, we are provided an appVersionCode values that we can use to handle restore data versioning. When the data is backed up, this value represents the version code of the app that was used to create the backup.

See also

  • The Backing up preferences and files to the cloud recipe
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset