Android 备份/恢复: 如何备份内部数据库?

我已经实现了一个 BackupAgentHelper使用提供的 FileBackupHelper备份和恢复本地数据库我有。这是通常与 ContentProviders一起使用的数据库,它驻留在 /data/data/yourpackage/databases/中。

人们会认为这是一个普遍的情况。然而,医生并不清楚该做什么: http://developer.android.com/guide/topics/data/backup.html。指向我的。在“ /databases/”中创建 db 文件,在我的 ContentProviders中引入了围绕任何 db 操作(比如 db.insert)的锁,甚至尝试在 onRestore()之前创建“ /databases/”目录,因为它在安装后不存在。

我曾经在一个不同的应用程序中成功地为 SharedPreferences实现了一个类似的解决方案。但是,当我在模拟器2.2中测试我的新实现时,我看到从日志执行到 LocalTransport的备份,以及执行恢复(并调用 onRestore())。然而,从未创建过 db 文件本身。

请注意,这都是在安装之后,在首次启动应用程序之前,在执行恢复之后。除此之外,我的测试策略是基于 http://developer.android.com/guide/topics/data/backup.html#Testing的。

还请注意,我不是说一些 sqlite 数据库我管理自己,也不是关于备份到 SDcard,自己的服务器或其他地方。

我确实在文档中看到过关于建议使用定制 BackupAgent的数据库的提及,但它似乎并不相关:

但是,您可能想要延长 如果需要,可以直接使用 BackupAgent: * 备份数据库中的数据 要还原时,用户 重新安装你的应用程序,你需要 构建自定义的 BackupAgent 读取适当的数据 备份操作,然后创建 表中插入数据 恢复操作。

说清楚点。

如果我真的需要自己做到 SQL 级别,那么我担心以下主题:

  • 打开数据库和事务。我不知道如何关闭我的应用程序的工作流之外的这样一个单例类。

  • 如何通知用户备份正在进行且数据库已锁定。这可能需要很长时间,所以我可能需要显示一个进度条。

  • 如何在恢复时做同样的事情。据我所知,还原可能发生在用户已经开始使用应用程序(并将数据输入数据库)的时候。因此,您不能假定只是将备份的数据还原到位(删除空数据或旧数据)。您必须以某种方式加入它,这对于任何非平凡的数据库来说都是不可能的,因为 id 的。

  • 如何在恢复完成后刷新应用程序,而不会让用户卡在某个现在无法到达的地方。

  • 我是否可以确定数据库已经在备份或还原时进行了升级?否则,预期的架构可能不匹配。

40397 次浏览

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.

One option will be to build it in application logic above the database. It actually screams for such levell I think. Not sure if you are doing it already but most people (despite android content manager cursor approach) will introduce some ORM mapping - either custom or some orm-lite approach. And what I would rather do in this case is:

  1. to make sure your application works fine when the app/data is added in the background with new data added/removed while the application already started
  2. to make some Java->protobuf or even simply java serialization mapping and write your own BackupHelper to read the data from the stream and simply add it to database....

So in this case rather than doing it on db level do it on application level.

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.

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));
}

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.

As of Android M, there is now a full-data backup/restore API available to apps. This new API includes an XML-based specification in the app manifest that lets the developer describe which files to back up in a direct semantic way: 'back up the database called "mydata.db"'. This new API is much easier for developers to use -- you don't have to keep track of diffs or request a backup pass explicitly, and the XML description of which files to back up means you often don't need to write any code at all.

(You can get involved even in a full-data backup/restore operation to get a callback when the restore happens, for example. It's flexible that way.)

See the Configuring Auto Backup for Apps section on developer.android.com for a description of how to use the new API.