【问题标题】:Android backup/restore: how to backup an internal database?Android 备份/恢复:如何备份内部数据库?
【发布时间】:2011-07-14 01:36:21
【问题描述】:

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

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

我过去曾在另一个应用程序中成功地为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 的原因,这是不可能的。

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

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

【问题讨论】:

  • 我也有同样的问题...
  • 有没有简单的备份孔db的方法?
  • 我需要使用 FileBackupHelper 备份一个文件夹。使用保存数据库的方法可以让我保存下面有很多子文件夹和子文件的文件夹吗?
  • 你有没有让这个工作正常?
  • 是的,见下文。我也回答了我自己的问题。

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


【解决方案1】:

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

其实就像添加一样简单:

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

致您的BackupAgentHelper

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

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

我想获得更多建议、对我们的期望的见解(什么是正确的解决方案),以及关于这可能会如何破坏的建议。

【讨论】:

  • 我可能应该补充一点,尽管这有点非官方,但它一直/一直运行良好。
  • 硬编码路径是邪恶的。
  • @pjv,那么您是否使每个数据库交互同步?我正在做同样的事情,并试图弄清楚我是否需​​要通过 db.close() 同步 db.open() 或只是 insert 语句或什么。
【解决方案2】:

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

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

【讨论】:

  • 将此与@pjv 给出的解决方案完美结合!它可能与规范所考虑的不完全一致……但效果很好!
  • 如果你有数据库普通文件要备份,那就没什么用了。
  • @Dan:在这种情况下使用多个代理。
  • @Pointer Null 您能否详细说明一下 getFilesDir(),为什么真的需要它,为什么?它是如何工作的?
【解决方案3】:

更简洁的方法是创建一个自定义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));
}

【讨论】:

  • @logray:该代码与问题中的代码完全相同。不必要的编辑。
  • @Linuxios 我同意,但是我认为最好为作者提供适当的归属,而不是盲目地粘贴代码......考虑到原始 OP/post 包含来自他的应用程序的链接。
  • 重要提示:用于备份文件的(文档)(developer.android.com/guide/topics/data/backup.html#Files) 声明该操作不是线程安全的,因此您应该同步您的数据库方法和您的备份代理
  • @yanchenko FileBackupHelper 文档指出:“注意:这只能用于小型配置文件,不能用于大型二进制文件。”因此,数据库通常会被视为大型二进制文件...
  • 这实际上不起作用! (除非我错过了什么......)。问题是您将数据库的绝对路径传递给 FileBackupHelper,但 FileBackupHelper 在路径前面加上文件目录路径,例如。 data/data//files 导致路径不正确,例如 data/data//files/data/data//databases/ 当你想要数据库绝对名称,并且由于超类预先添加了文件目录,因此您需要使路径相对于文件目录。
【解决方案4】:

使用FileBackupHelper 备份/恢复sqlite db 会引发一些严重的问题:
1. 如果应用程序使用从ContentProvider.query() 检索的光标并且备份代理尝试覆盖整个文件,会发生什么情况?
2. link 是完美(低熵;)测试的一个很好的例子。您卸载应用程序,再次安装它并恢复备份。然而,生活可能是残酷的。看看link。让我们想象一下用户购买新设备的场景。由于它没有自己的集合,因此备份代理使用其他设备的集合。该应用程序已安装,并且您的 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 中完成。如果你自己做光标对象映射,序列化一个项目就像在代表你的实体的类上实现Serializabletransient字段_id一样简单。

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

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

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

希望这会有所帮助。

【讨论】:

  • 你重复了一些有效的问题。但我看不出序列化数据库如何解决问题。它对并发问题没有帮助。而且如果你不能写脚本来降级数据库,那么你也不能写脚本来反序列化旧数据。
  • @pjv 如果您使用 ContentResolver,它会为您解决并发问题。
  • @IgorGanapolsky 是的,我认为这是真的。但是,如果序列化速度足够慢并且用户已经在使用应用程序并输入数据,它可能会与您正在恢复的数据发生冲突。您可以在用户访问应用程序之前自动执行此操作吗?
  • @pjv 您可以注册一个 ContentObserver 以同步您正在观察的数据以收到通知。因此您不必担心冲突。
  • 您关于数据库版本的观点很好 - 我想如果您保存您的数据库版本(例如,在 sharedpreferences 中,假设您也备份它)那么当您恢复时,您应该能够使用在 onDowngrade() 方法中将数据库升级到其当前版本的现有逻辑。如果您还没有升级代码,那么无论如何您都不会为保留数据而烦恼!
【解决方案5】:

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

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

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

【讨论】:

  • 这仅适用于Android 6.0 (API 23),如果用户有旧版本仍然需要实现键值备份。
【解决方案6】:

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

  1. 确保您的应用程序正常工作 添加应用程序/数据时很好 新数据的背景 在应用程序中添加/删除 已经开始了
  2. 做一些 Java->protobuf 甚至简单的 java 序列化映射并编写您的 自带BackupHelper读取数据 从流中,只需将其添加到 数据库....

所以在这种情况下,而不是在数据库级别执行它,而是在应用程序级别执行它。

【讨论】:

  • 问题不在于如何将数据从数据库中获取到 BackupHelperAgent 中,反之亦然。请阅读我问题末尾的 5 个项目符号。
  • @JarekPotiuk 你说:“大多数人(尽管 android 内容管理器光标方法)”。但是是什么让您认为大多数人会避免使用 ContentProvider 和 ContentResolver 来访问数据库?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-04-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-23
  • 2014-09-03
相关资源
最近更新 更多