Android backup/restore: how to backup an internal database?

Solution 1:

After revisiting my question, I was able to get it to work after looking at how ConnectBot does it. Thanks Kenny and Jeffrey!

It's actually as easy as adding:

FileBackupHelper hosts = new FileBackupHelper(this,
    "../databases/" + HostDatabase.DB_NAME);
addHelper(HostDatabase.DB_NAME, hosts);

to your BackupAgentHelper.

The point I was missing was the fact that you'd have to use a relative path with "../databases/".

Still, this is by no means a perfect solution. The docs for FileBackupHelper mention for instance: "FileBackupHelper should be used only with small configuration files, not large binary files.", the latter being the case with SQLite databases.

I'd like to get more suggestions, insights into what is expected of us (what is the proper solution), and advice on how this might break.

Solution 2:

Here's yet cleaner way to backup databases as files. No hardcoded paths.

class MyBackupAgent extends BackupAgentHelper{
   private static final String DB_NAME = "my_db";

   @Override
   public void onCreate(){
      FileBackupHelper dbs = new FileBackupHelper(this, DB_NAME);
      addHelper("dbs", dbs);
   }

   @Override
   public File getFilesDir(){
      File path = getDatabasePath(DB_NAME);
      return path.getParentFile();
   }
}

Note: it overrides getFilesDir so that FileBackupHelper works in databases dir, not files dir.

Another hint: you may also use databaseList to get all your DB's and feed names from this list (without parent path) into FileBackupHelper. Then all app's DB's would be saved in backup.

Solution 3:

A cleaner approach would be to create a custom BackupHelper:

public class DbBackupHelper extends FileBackupHelper {

    public DbBackupHelper(Context ctx, String dbName) {
        super(ctx, ctx.getDatabasePath(dbName).getAbsolutePath());
    }
}

and then add it to BackupAgentHelper:

public void onCreate() {
    addHelper(DATABASE, new DbBackupHelper(this, DB.FILE));
}

Solution 4:

Using FileBackupHelper to backup/restore sqlite db raises some serious questions:
1. What happens if the app uses cursor retrieved from ContentProvider.query() and backup agent tries to override the whole file?
2. The link is a nice example of perfect (low entrophy ;) testing. You uninstall app, install it again and the backup is restored. However life can be brutal. Take a look at link. Let's imagine scenario when a user buys a new device. Since it doesn't have its own set, the backup agent uses other device's set. The app is installed and your backupHelper retrieves old file with db version schema lower than the current. SQLiteOpenHelper calls onDowngrade with the default implementation:

public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    throw new SQLiteException("Can't downgrade database from version " +
            oldVersion + " to " + newVersion);
}

No matter what the user does he/she can't use your app on the new device.

I'd suggest using ContentResolver to get data -> serialize (without _ids) for backup and deserialize -> insert data for restore.

Note: get/insert data is done through ContentResolver thus avoiding cuncurrency issues. Serializing is done in your backupAgent. If you do your own cursor<->object mapping serializing an item can be as simple as implementing Serializable with transient field _id on the class representing your entity.

I'd also use bulk insert i.e. ContentProviderOperation example and CursorLoader.setUpdateThrottle so that the app is not stuck with restarting loader on data change during backup restore process.

If you happen do be in a situation of a downgrade, you can choose either to abort restore data or restore and update ContentResolver with fields relevant to the downgraded version.

I agree that the subject is not easy, not well explained in docs and some questions still remain like bulk data size etc.

Hope this helps.