80

我已经实施了BackupAgentHelper使用提供的FileBackupHelper来备份和恢复我拥有的本地数据库。这是您通常与ContentProviders一起使用的数据库,它位于/data/data/yourpackage/databases/Android备份/恢复:如何备份内部数据库?

有人会认为这是一种常见的情况。但是文档不知道该怎么做:http://developer.android.com/guide/topics/data/backup.html这些典型的数据库没有专门的BackupHelper。因此,我用了FileBackupHelper,就指着我的.db文件中的“/databases/”,在我ContentProviders介绍各地的任何数据库操作(如db.insert)锁,onRestore()之前甚至试图打造“/databases/”目录,因为它不存在安装后。

我已经实现了SharedPreferences类似的解决方案成功地在过去不同的应用程序。然而,当我在仿真器2.2中测试新的实现时,我看到从日志执行到LocalTransport的备份以及执行恢复(并调用onRestore())。 然而,数据库文件本身从未被创建。

请注意,这是已被执行后,还原安装所有后,并首家推出应用程序之前,。除此之外,我的测试策略是基于http://developer.android.com/guide/topics/data/backup.html#Testing

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

我没有看到的文档有关数据库的建议使用自定义BackupAgent一提,但它似乎并不相关:

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

请澄清一下。

如果我真的需要做的是自己到SQL的水平,那么我担心了以下主题:

  • 打开数据库和交易。我不知道如何从我的应用工作流程之外的单一类中关闭它们。

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

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

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

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

+0

我有同样的问题... – whynot 2011-05-31 11:47:03

+0

有没有办法简单备份洞db? – Jin35 2011-12-29 06:01:09

+0

我需要使用FileBackupHelper备份一个文件夹。将使用保存数据库的方法,让我保存该文件夹下有很多子文件夹和子文件? – coolcool1994 2014-12-17 12:18:37

回答

20

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

链接404 ,,, – 2013-02-12 05:29:16

+0

编辑:添加从旧分支回来的链接 – logray 2013-02-12 23:37:11

+2

@logray:该代码与问题中完全相同。不必要的编辑。 – Linuxios 2013-02-12 23:48:32

33

重新审视我的问题后,我看到how ConnectBot does it后能够得到它的工作。感谢Kenny和Jeffrey!

它实际上是那么容易,因为加:

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

BackupAgentHelper

我错过的一点是,你必须使用“../databases/”的相对路径。

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

我希望得到更多的建议,深入了解我们的期望(什么是正确的解决方案)以及关于如何破解的建议。

+1

我应该补充一点,即使这有点不正式,但它一直都很好。 – pjv 2011-12-28 11:57:40

+6

硬编码路径是邪恶的。 – 2012-03-15 11:22:42

+0

@mice:我同意。 – pjv 2012-03-20 19:20:53

0

一个选项是将其构建在数据库上方的应用程序逻辑中。它实际上为我想的这样的levell尖叫。 不知道你是否已经这样做了,但大多数人(尽管android内容管理器光标方法)会引入一些ORM映射 - 无论是自定义还是一些orm-lite方法。而我宁愿做在这种情况下是:

  1. ,以确保您的应用程序的工作,当应用程序/数据在 添加新的数据 后台添加/删除,而应用程序 已经开始
  2. 罚款
  3. 做出一些 基于Java>的protobuf甚至干脆在java 序列化映射和写你自己的 向BackupHelper从流中读取数据 并简单地把它添加到数据库 ....

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

+0

问题不在于如何将数据从数据库中获取到BackupHelperAgent中,反之亦然。请在我的问题结尾处阅读5个子弹。 – pjv 2011-06-19 21:41:10

+0

@JarekPotiuk你说过:“大多数人(尽管android内容管理器光标方式)”。但是,是什么让你认为大多数人会避免使用ContentProvider和ContentResolver来访问数据库? – 2014-01-17 20:08:15

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

+0

将此与@pjv给出的解决方案结合起来完美无缺!它可能并不完全符合规范的要求......但它工作得很好! – Tom 2012-04-17 14:37:00

+1

如果您有数据库**和**要备份的普通文件,这并没有多大用处。 – 2013-03-21 17:03:16

+1

@Dan:在这种情况下使用多个代理。 – pjv 2013-11-05 17:25:18

7

使用FileBackupHelper备份/恢复的SQLite数据库提出了一些严肃的问题:
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获取数据 - >序列化(没有_id)用于备份和反序列化 - >插入数据进行还原。

注意:通过ContentResolver完成获取/插入数据,从而避免出现cuncurrency问题。序列化在您的backupAgent中完成。如果你自己做光标< - >对象映射序列化一个项目可以很简单,只需在代表实体的类上实现Serializabletransient field_id即可。

我还使用批量插入即ContentProviderOperationexampleCursorLoader.setUpdateThrottle从而使应用程序无法坚持备份期间重新启动上的数据变化装载机恢复过程。

如果您碰巧遇到降级的情况,您可以选择中止还原数据,或者使用与降级版本相关的字段还原和更新ContentResolver。

我同意,对象是不容易的,在文档没有得到很好的解释,有些问题仍然没有类似散装数据大小等

希望这有助于。

+0

重复一些有效的问题。但我不明白序列化数据库如何解决问题。它对并发问题没有帮助。如果您不能编写降级数据库的脚本,则无法编写脚本来反序列化旧数据。 – pjv 2013-11-05 17:28:27

+0

@pjv如果您使用ContentResolver,它会为您解决并发问题。 – 2014-01-17 20:05:39

+0

@IgorGanapolsky是的,我认为这是事实。但是,如果序列化足够慢并且用户已在使用该应用程序并输入数据,则可能与正在恢复的数据冲突。你能做到原子,并在用户访问应用程序之前? – pjv 2014-01-19 11:49:27

3

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

(您可以涉足即使在完全数据备份/恢复操作以获取一个回调时恢复情况,例如,它是灵活的方式。)

查看开发商的Configuring Auto Backup for Apps部分。 android.com了解如何使用新API的说明。

+0

这仅适用于Android 6.0(API 23),如果用户的版本较旧,还需要实施Key Value备份。 – 2017-11-29 22:15:30