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

时间:2011-03-12 13:58:59

标签: android database restore database-restore android-backup-service

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

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

我过去曾在其他应用中为onRestore()成功实施了类似的解决方案。但是,当我在模拟器2.2中测试我的新实现时,我看到从日志中执行了SharedPreferences的备份,以及正在执行的恢复(并且LocalTransport被调用)。 然而,db文件本身永远不会被创建。

请注意,这是在安装之后以及在首次启动应用程序之后执行还原之后的全部内容。除此之外,我的测试策略基于http://developer.android.com/guide/topics/data/backup.html#Testing

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

我确实在文档中提到有关建议使用自定义onRestore()的数据库,但它似乎并不相关:

  

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

请说清楚。

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

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

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

  • 如何在恢复时执行相同的操作。据我了解,恢复可能发生在用户已经开始使用应用程序(并将数据输入数据库)时。所以你不能假定只是恢复备份数据(删除空数据或旧数据)。你必须以某种方式加入它,由于id的原因,任何非平凡的数据库都是不可能的。

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

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

6 个答案:

答案 0 :(得分:34)

在重新审视我的问题之后,我在查看how ConnectBot does it之后能够让它工作。谢谢肯尼和杰弗里!

这实际上就像添加:

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

BackupAgentHelper

我遗漏的一点是,您必须使用“../databases/”的相对路径。

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

我想获得更多建议,洞察我们的期望(适当的解决方案是什么),以及如何解决这个问题的建议。

答案 1 :(得分:23)

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

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。然后所有应用程序的数据库都将保存在备份中。

答案 2 :(得分: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));
}

答案 3 :(得分:7)

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

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

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

我建议使用ContentResolver来获取数据 - >序列化(没有_id s)进行备份和反序列化 - >插入数据以进行恢复。

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

我还会使用批量插入,即ContentProviderOperation exampleCursorLoader.setUpdateThrottle,以便应用程序在备份恢复过程中不会因重新启动数据更改而加载。

如果您确实处于降级状态,您可以选择中止还原数据,或者使用与降级版本相关的字段还原和更新ContentResolver。

我同意这个主题不容易,在文档中没有很好地解释,有些问题仍然像批量数据大小等。

希望这有帮助。

答案 4 :(得分:3)

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

(例如,你可以参与完整数据备份/恢复操作,以便在恢复发生时获得回调。这样就很灵活。)

有关如何使用新API的说明,请参阅developer.android.com上的Configuring Auto Backup for Apps部分。

答案 5 :(得分:0)

一种选择是在数据库上方的应用程序逻辑中构建它。我觉得它实际上是为了这样的事情而尖叫。 不确定你是否已经这样做但是大多数人(尽管安卓内容管理器游标方法)将引入一些ORM映射 - 自定义或一些orm-lite方法。在这种情况下我宁愿做的是:

  1. 确保您的应用程序正常运行 添加app / data时很好 新数据的背景 在应用程序中添加/删除 已经开始
  2. 做一些 Java-> protobuf甚至只是java 序列化映射并编写您的 拥有BackupHelper来读取数据 从流中,只需将其添加到 数据库....
  3. 因此,在这种情况下,而不是在数据库级别上执行此操作,请在应用程序级别执行此操作。