Android备份/还原:如何备份内部数据库?


86

我已经实现了BackupAgentHelper使用提供的FileBackupHelper来备份和还原我拥有的本机数据库。这是您通常与一起使用ContentProviders并驻留在中的数据库/data/data/yourpackage/databases/

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

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

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

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

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

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

请清楚一些。

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

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

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

  • 恢复时该如何做。据我了解,仅当用户已经开始使用该应用程序(并将数据输入数据库)时,还原才可能发生。因此,您不能假定只将备份的数据还原到位(删除空白或旧数据)。您必须以某种方式将其加入其中,由于id的原因,对于任何不重要的数据库都是不可能的。

  • 还原完成后如何刷新应用程序而又不会使用户陷入某些无法访问的点。

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


我有同样的问题……

有没有办法简单备份db的孔?
2011年

我需要使用FileBackupHelper备份文件夹。是否可以使用保存数据库方法来保存该文件夹,该文件夹下有很多子文件夹和子文件?
coolcool1994 2014年

你有没有让它正常工作?
DeNitE Appz 2015年

是的,请参见下文。我也回答了我自己的问题。
pjv

Answers:


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

2
@logray:该代码与问题中的代码完全相同。不必要的编辑。
Linuxios

@Linuxios我同意,但是我认为最好为作者提供适当的署名,而不是仅仅盲目粘贴代码...考虑到原始的OP / post包括他应用程序的链接。
logray

3
重要提示:用于备份文件的(documentation)(developer.android.com/guide/topics/data/backup.html#Files)指出该操作不是线程安全的,因此您应该同步数据库方法和备份代理
nicopico

@yanchenko FileBackupHelper的文档指出:“注意:此选项只能用于小型配置文件,而不能用于大型二进制文件。” 因此,数据库通常可以视为大型二进制文件...
IgorGanapolsky 2014年

这实际上是行不通的!(除非我错过了某件事...)。问题是您将db的绝对路径传递给FileBackupHelper,但是FileBackupHelper将该路径加上了文件目录路径,例如。数据/数据/ <您的软件包> /文件,当您想要的只是路径时,会导致路径不正确,如数据/数据/ <您的软件包> /文件/数据/数据/ <您的软件包> /数据库/ DB绝对名称,并且由于超类位于文件目录的前面,因此您需要建立相对于文件目录的路径。
bitrock 2014年

34

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

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

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

给你BackupAgentHelper

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

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

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


1
我可能应该补充一点,尽管这不是很正式,但一直以来都运行得很好。
pjv 2011年

6
硬编码路径是邪恶的。
指针为空

@pjv,那么您是否使每个数据库交互都同步?我在做同样的事情,并试图弄清楚是否需要db.open()通过语句或db.close()仅进行同步insert
NSouth 2014年

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在数据库dir(而不是文件dir)中工作。

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


将此与@pjv提供的解决方案完美结合!可能不是规格中所考虑的确切内容……但是效果很好!
汤姆(Tom)

1
如果您有要备份的数据库普通文件,那没什么用。
Dan Hulme

1
@Dan:在这种情况下,请使用多个代理。
pjv

@Pointer Null您能否详细说明一个getFilesDir(),为什么真正需要它,为什么?它是如何工作的?
powder366

7

使用FileBackupHelper备份/恢复的SQLite数据库提出了一些严肃的问题:
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中完成。如果您执行自己的cursor <-> object映射,则序列化项目就像在代表您的实体的类上Serializable使用transient字段_id实现一样简单。

我还将使用大容量插入(ContentProviderOperation 例如example)CursorLoader.setUpdateThrottle以便在备份还原过程中,应用程序不会因数据更改而重启加载程序。

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

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

希望这可以帮助。


您重复一些有效的问题。但是我看不到序列化数据库如何解决问题。它对并发问题没有帮助。而且,如果您无法编写用于降级数据库的脚本,那么您也无法编写用于反序列化旧数据的脚本。
pjv

@pjv如果使用ContentResolver,它将为您解决并发问题。
IgorGanapolsky 2014年

@IgorGanapolsky是的,我认为是事实。但是,如果序列化的速度足够慢,并且用户已经在使用该应用程序并输入数据,则它可能与您正在还原的数据冲突。您可以在用户访问应用程序之前自动进行此操作吗?
pjv 2014年

@pjv您可以注册ContentObserver来同步观察到的数据,以通知它们。因此,您不必担心冲突。
IgorGanapolsky 2014年

关于数据库版本的观点很不错-我想如果您保存数据库版本(例如,在共享首选项中,假设您也对其进行了备份),那么在还原时,您应该能够使用现有逻辑将数据库升级到其版本。 onDowngrade()方法中的当前版本。如果您还没有升级代码,那么就不必担心保留数据!
Ben Neill 2014年

3

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

(例如,您甚至可以参与全数据备份/还原操作,以在发生还原时获取回调。这种方式非常灵活。)

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


这仅适用于Android 6.0(API 23),如果用户使用的是旧版本,则仍必须实施键值备份。
真爱

0

一种选择是在数据库上方的应用程序逻辑中构建它。我认为,它实际上为这样的高声尖叫。不知道您是否已经在做,但是大多数人(尽管使用了Android Content Manager游标方法)会引入一些ORM映射-自定义或某种orm-lite方法。在这种情况下,我宁愿做的是:

  1. 确保在后台启动应用程序/数据并在应用程序已经启动时添加/删除新数据时,您的应用程序能够正常运行
  2. 进行一些Java-> protobuf甚至简单的Java序列化映射,并编写自己的BackupHelper来从流中读取数据并将其简单地添加到数据库中。

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


问题不在于如何将数据从数据库获取到BackupHelperAgent中,反之亦然。请阅读我问题末尾的5个项目符号。
pjv 2011年

@JarekPotiuk您说:“大多数人(尽管使用Android内容管理器的游标方法)”。但是,是什么让您认为大多数人会避免使用ContentProvider和ContentResolver进行数据库访问呢?
IgorGanapolsky 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.