Android备份/还原:如何备份内部数据库?
我已经使用提供的FileBackupHelper
实现了一个BackupAgentHelper
来备份和恢复我拥有的本地数据库。 这是您通常与ContentProviders
一起使用的数据库,位于/data/data/yourpackage/databases/
。
有人会认为这是常见的情况。 但是文档不清楚该怎么做: http : //developer.android.com/guide/topics/data/backup.html 。 这些典型的数据库没有专门的BackupHelper
。 因此,我使用了FileBackupHelper
,将其指向“ /databases/
”中的.db文件,在我的ContentProviders
引入了任何数据库操作(如db.insert
)的锁,甚至尝试在onRestore()
之前创build“ /databases/
”目录onRestore()
因为安装后它不存在。
过去,我在其他应用程序中成功实施了类似的SharedPreferences
解决scheme。 但是,当我在emulator-2.2中testing新的实现时,我看到从日志执行到LocalTransport
的备份,以及正在执行的恢复(并onRestore()
)。 然而,数据库文件本身从来没有创build。
请注意,这完全是在安装之后,在应用程序第一次启动之前,在执行还原之后。 除此之外,我的testing策略是基于http://developer.android.com/guide/topics/data/backup.html#Testing 。
另外请注意,我不是在谈论一些我自己pipe理的sqlite数据库,也不是关于备份到SD卡,自己的服务器或其他地方。
我在文档中看到了一些关于build议使用自定义的BackupAgent
数据库,但似乎并不相关:
但是,如果需要,可能需要直接扩展BackupAgent:*备份数据库中的数据。 如果您在用户重新安装应用程序时想要还原SQLite数据库,则需要构build自定义的BackupAgent,以在备份操作期间读取相应的数据,然后创build表并在还原操作期间插入数据。
请澄清一下。
如果我真的需要自己做到SQL级别,那么我担心以下主题:
-
打开数据库和事务。 我不知道如何从我的应用程序的工作stream程之外的单一类中closures它们。
-
如何通知用户正在进行备份和数据库被locking。 这可能需要很长时间,所以我可能需要显示进度条。
-
如何做同样的恢复。 据我所知,恢复可能发生在用户已经开始使用应用程序(并input数据到数据库)。 所以你不能假定只是恢复备份的数据(删除空的或旧的数据)。 你将不得不以某种方式join它,因为任何非平凡的数据库是不可能的,由于ID的。
-
如何在还原完成后刷新应用程序,而不会让用户卡在某些 – 现在无法访问的点上。
-
我可以确定数据库已经在备份或恢复中升级了吗? 否则预期的模式可能不匹配。
一个更清洁的方法是创build一个自定义的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)); }
在重新回顾我的问题之后,我看了一下ConnectBot是如何工作的。 感谢Kenny和Jeffrey!
这实际上就像添加:
FileBackupHelper hosts = new FileBackupHelper(this, "../databases/" + HostDatabase.DB_NAME); addHelper(HostDatabase.DB_NAME, hosts);
到您的BackupAgentHelper
。
我遗漏的一点是,你必须使用“ ../databases/
”的相对path。
不过,这绝不是一个完美的解决scheme。 FileBackupHelper
的文档中提到了例如:“ FileBackupHelper
只能用于小configuration文件,而不是大的二进制文件 ”,后者就是SQLite数据库的情况。
我希望得到更多的build议,深入了解我们的期望(什么是适当的解决scheme),以及关于如何破解的build议。
这是更简洁的方式来备份数据库作为文件。 没有硬编码的path。
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从这个列表(没有父path)到FileBackupHelper获取所有的数据库和饲料名称。 然后,所有应用程序的数据库将被保存在备份。
使用FileBackupHelper
备份/恢复sqlite数据库提出了一些严重的问题:
1.如果应用程序使用从ContentProvider.query()
检索的游标并且备份代理尝试覆盖整个文件,会发生什么情况?
这个链接是完美(低熵)testing的一个很好的例子。 您卸载应用程序,再次安装它,备份被恢复。 然而,生活可能是残酷的。 看看链接 。 让我们来想象一下,用户购买新设备的场景。 由于它没有自己的设置,所以备份代理使用其他设备的集合。 该应用程序已安装,并且您的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); }
无论用户做什么,他/她都不能在新设备上使用您的应用程序。
我build议使用ContentResolver
获取数据 – >序列化(不带_id
)备份和反序列化 – >插入数据进行恢复。
注意:获取/插入数据是通过ContentResolver完成的,从而避免了cuncurrency问题。 序列化是在你的backupAgent中完成的。 如果你做你自己的游标< – >对象映射序列化一个项目可以像在表示你的实体的类上使用transient
字段_id实现Serializable
一样简单。
我也使用批量插入,即ContentProviderOperation
示例和CursorLoader.setUpdateThrottle
以便该应用程序不会卡住重新启动加载器在备份还原过程中的数据更改。
如果遇到降级情况,可以select中止恢复数据,或者使用与降级版本相关的字段还原和更新ContentResolver。
我同意这个主题不容易,在文档中没有很好的解释,一些问题仍然像批量数据大小一样。
希望这可以帮助。
从Android M开始,现在有一个全数据备份/恢复API可用于应用程序。 这个新的API在应用程序清单中包含一个基于XML的规范,让开发人员可以直接语义的方式来描述备份哪些文件:“备份名为”mydata.db“的数据库。 这个新的API对于开发者来说更容易使用 – 你不必跟踪差异或者明确地请求一个备份传递,并且XML文件的备份意味着你通常不需要编写任何代码在所有。
(例如,即使在全数据备份/恢复操作中,您也可以参与到恢复过程中的callback中,这种方式非常灵活。)
有关如何使用新API的说明,请参见developer.android.com上的“ configuration应用程序的自动备份”部分。
一种select是将其构build在数据库上方的应用程序逻辑中。 它实际上是我想的这样的levell尖叫。 不知道你是否已经做了,但大多数人(尽pipeandroid内容pipe理器光标方法)将引入一些ORM映射 – 无论是自定义的还是一些orm-lite的方法。 而我在这种情况下宁愿做的是:
- 以确保您的应用程序工作正常,当应用程序/数据在后台添加新的数据添加/删除,而应用程序已经开始时添加
- 使一些Java-> protobuf甚至简单的Java序列化映射,并写你自己的BackupHelper从stream读取数据,并简单地将其添加到数据库….
所以在这种情况下,而不是在数据库级别做它在应用程序级别。