87

我已经实现了BackupAgentHelper使用提供FileBackupHelper的备份和恢复我拥有的本机数据库。这是您通常与它一起使用的数据库ContentProviders,它位于/data/data/yourpackage/databases/.

有人会认为这是一种常见的情况。但是,文档并不清楚该怎么做:http: //developer.android.com/guide/topics/data/backup.html没有BackupHelper专门针对这些典型数据库的。因此,我使用了FileBackupHelper.db 文件,将其指向“ /databases/”中的我的 .db 文件,在我的 .db 操作(例如db.insert)周围引入了锁ContentProviders,甚至尝试在/databases/之前创建“”目录,onRestore()因为它在安装后不存在。

SharedPreferences过去,我已经在不同的应用程序中成功实施了类似的解决方案。但是,当我在 emulator-2.2 中测试我的新实现时,我看到正在LocalTransport从日志中执行备份,以及正在执行(和onRestore()调用)恢复。然而,永远不会创建 db 文件本身。

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

另请注意,我不是在谈论我自己管理的一些 sqlite 数据库,也不是在谈论备份到 SD 卡、自己的服务器或其他地方。

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

但是,如果需要,您可能希望直接扩展 BackupAgent: * 备份数据库中的数据。如果您有一个 SQLite 数据库要在用户重新安装您的应用程序时恢复,您需要构建一个自定义 BackupAgent,它在备份操作期间读取适当的数据,然后创建您的表并在恢复操作期间插入数据。

请澄清一些。

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

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

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

  • 如何在还原时执行相同操作。据我了解,恢复可能发生在用户已经开始使用应用程序(并将数据输入数据库)时。因此,您不能假设只将备份数据恢复到位(删除空数据或旧数据)。您必须以某种方式加入它,对于任何重要的数据库来说,由于 id 的原因,这是不可能的。

  • 如何在还原完成后刷新应用程序,而不会让用户卡在某个 - 现在 - 无法到达的点。

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

4

6 回答 6

34

重新审视我的问题后,在查看了 ConnectBot 是如何做到的之后,我能够让它工作。谢谢肯尼和杰弗里!

它实际上就像添加一样简单:

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

到你的BackupAgentHelper.

我缺少的一点是您必须使用带有“ ../databases/”的相对路径。

尽管如此,这绝不是一个完美的解决方案。FileBackupHelper例如,提到的文档:“FileBackupHelper应该只用于小型配置文件,而不是大型二进制文件。 ”,后者是 SQLite 数据库的情况。

我想获得更多建议,深入了解我们对我们的期望(什么是正确的解决方案),以及关于如何打破这种情况的建议。

于 2011-06-13T15:08:35.067 回答
22

这是将数据库备份为文件的更简洁的方法。没有硬编码的路径。

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

注意:它覆盖getFilesDir以便 FileBackupHelper 在数据库目录中工作,而不是在文件目录中。

另一个提示:您也可以使用databaseList将您的所有数据库和提要名称从此列表(没有父路径)中获取到 FileBackupHelper。然后所有应用程序的数据库将保存在备份中。

于 2012-03-15T11:21:37.450 回答
21

一种更清洁的方法是创建一个自定义BackupHelper

public class DbBackupHelper extends FileBackupHelper {

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

然后将其添加到BackupAgentHelper

public void onCreate() {
    addHelper(DATABASE, new DbBackupHelper(this, DB.FILE));
}
于 2012-03-31T14:35:08.640 回答
8

使用FileBackupHelper备份/恢复 sqlite db 会引发一些严重
的问题: 1. 如果应用程序使用从检索到的游标ContentProvider.query()并且备份代理尝试覆盖整个文件会发生什么?
2.链接是完美(低熵;)测试的一个很好的例子。您卸载应用程序,再次安装它并恢复备份。然而,生活可能是残酷的。看看链接。让我们想象一下用户购买新设备的场景。由于它没有自己的集合,因此备份代理使用其他设备的集合。该应用程序已安装,并且您的 backupHelper 检索数据库版本架构低于当前的旧文件。使用默认实现SQLiteOpenHelper调用:onDowngrade

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

无论用户做什么,他/她都无法在新设备上使用您的应用程序。

我建议使用ContentResolver获取数据-> 序列化(不带_ids)以进行备份和反序列化-> 插入数据以进行恢复。

注意:获取/插入数据是通过 ContentResolver 完成的,从而避免了并发问题。序列化在您的 backupAgent 中完成。如果您自己进行光标<->对象映射,则序列化项目可以像在代表您的实体的类上Serializable使用字段 _id 实现一样简单。transient

我还将使用批量插入即ContentProviderOperation 示例CursorLoader.setUpdateThrottle这样应用程序就不会在备份还原过程中因数据更改而重新启动加载程序。

如果您确实处于降级的情况,您可以选择中止恢复数据或使用与降级版本相关的字段来恢复和更新 ContentResolver。

我同意这个主题并不容易,在文档中没有得到很好的解释,一些问题仍然存在,比如批量数据大小等。

希望这可以帮助。

于 2013-10-31T10:29:10.293 回答
5

从 Android M 开始,现在有一个可供应用使用的完整数据备份/恢复 API。这个新的 API 在应用程序清单中包含一个基于 XML 的规范,它允许开发人员以直接语义方式描述要备份哪些文件:“备份名为“mydata.db”的数据库”。这个新的 API 对开发人员来说更容易使用——您不必跟踪差异或明确请求备份通行证,而要备份哪些文件的 XML 描述意味着您通常不需要编写任何代码一点也不。

(例如,您甚至可以参与完整数据备份/恢复操作以在恢复发生时获得回调。这种方式很灵活。)

有关如何使用新 API 的说明,请参阅developer.android.com 上的为应用程序配置自动备份部分。

于 2015-12-27T04:39:13.400 回答
0

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.

于 2011-06-19T20:37:51.220 回答