How to properly close and reopen Room Database
您好,我有2个应用程序仅通过将数据库文件复制进sdcard或从sdcard复制文件来依赖于制作和恢复应用程序数据库的备份,并且在弄清楚如何将Room Database单例关闭后很难解决。制作数据库的副本。
构建数据库:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | @Database(version = 15, exportSchema = true, entities = [list of entities]) abstract class AppDatabase : RoomDatabase() { //list of DAOs companion object { @Volatile private var INSTANCE: AppDatabase? = null fun getInstance(context: Context): AppDatabase = INSTANCE ?: synchronized(this) { INSTANCE ?: buildDatabase(context).also { INSTANCE = it } } private fun buildDatabase(context: Context) = Room.databaseBuilder( context.applicationContext, AppDatabase::class.java, "Fazendao.sqlitedb" ) .addMigrations(Migration1315) .build() } } |
关闭数据库:
1 2 3 4 5 | fun closeDatabase() { if(db.isOpen) { db.openHelper.close() } } |
复制数据库文件(在ViewModel内部):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | fun exportaBkpObservable(nome: String, auto: String, storage: File, database: File) { disposable.clear() setFlagsNull() flagSubject.onNext(false) disposable.add( Observable.fromCallable { repo.recordBkpName(nome) } .subscribeOn(Schedulers.io()) .flatMap { return@flatMap try { //closing the database repo.closeDatabase() Observable.just( database.copyTo(File(storage, auto), true) ) .flatMap { val myDb = SQLiteDatabase.openOrCreateDatabase(it, null) val ok = myDb.isDatabaseIntegrityOk if(myDb.isOpen) myDb.close() if(ok) { Observable.just(ok) } else { Observable.error<Throwable>(Throwable("CORRUPTED DATABASE")) } } } catch (t: Throwable) { Observable.error<Throwable>(t) } } .subscribe( {}, { errorFlag ="exportDB:" + it.message errorSubject.onNext("exportDB:" + it.message) }, { //trying to reopen database repo.openDatabase() trueFlag = true flagSubject.onNext(true) } ) ) } |
存储库是将AppDatabase注入的存储库,而该存储库又被注入到ViewModelFactory中。
注入量:
1 2 3 4 5 6 7 8 9 10 11 12 13 | object MainInjection { private fun providesIORepo(context: Context): IORepo { return IORepo(AppDatabase.getInstance(context)) } fun provideIOViewModelFactory(context: Context): IOViewModelFactory { val data = providesIORepo(context) return IOViewModelFactory(data) } } |
并在AppCompatActivity中onCreate:
1 2 | val modelFactory = MainInjection.provideIOViewModelFactory(this) viewModel = ViewModelProviders.of(this, modelFactory).get(IOViewModel::class.java) |
重新打开数据库:
1 2 3 4 5 | fun openDatabase() { if(!db.isOpen){ db.openHelper.writableDatabase } } |
现在出现错误信息:
尝试重新打开数据库:
1 | E/ROOM: Invalidation tracker is initialized twice :/. |
因此,当我尝试从另一个函数访问它时发生崩溃:
1 | Cannot perform this operation because the connection pool has been closed. |
有时关闭数据库后,我也有:
1 | E/ROOM: Cannot run invalidation tracker. Is the db closed? |
在这篇文章中,作者逐步从SQLite迁移到Room,作者每次打开和关闭数据库都会打开和关闭数据库,所以我不明白为什么我的实现无法正常工作。
那我在哪里错了?是否也有一种方法可以停用InvalidationTracker?
我每次复制数据库文件时,都应使用以下代码关闭数据库并清除Room实例。安全吗?:
1 2 3 4 5 6 7 8 | fun destroyInstance() { if (INSTANCE?.isOpen == true) { INSTANCE?.close() } INSTANCE = null } |
感谢您的关注。
好吧,我开始使用以下代码关闭数据库:
1 2 3 4 5 6 7 8 | fun destroyInstance() { if (INSTANCE?.isOpen == true) { INSTANCE?.close() } INSTANCE = null } |
并实现导入数据库,如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | fun importaBkpObservable(origin: File, database: File) { disposable.clear() setFlagsNull() flagSubject.onNext(false) disposable.add( Observable.fromCallable { try { repo.closeDatabase() val myDb = SQLiteDatabase.openOrCreateDatabase(origin, null) val ok = myDb.isDatabaseIntegrityOk if (myDb.isOpen) myDb.close() if(ok) { origin.copyTo(database, true) } else { "CORRUPTED DATABASE" } } catch (t: Throwable) { t.message } } .subscribeOn(Schedulers.io()) .subscribe( { if(it != null) { if(it is String) { errorFlag ="exportDB: $it" errorSubject.onNext("exportDB: $it") } else { trueFlag = true flagSubject.onNext(true) } } else { errorFlag ="exportDB: GENERIC" errorSubject.onNext("exportDB: GENERIC") } }, { errorFlag ="exportDB: ${it.message}" errorSubject.onNext("exportDB: ${it.message}") } ) ) } |
我曾经通过startActivityForResult()从我的主要活动导航到导入/导出活动,但现在已更改为startActivity()在此调用后完成我的主要活动。导入/导出完成后,我使用startActivity()调用我的主要活动,然后完成导入/导出活动。
这样,我的主要活动ViewModel便用新的AppDatabase实例再次实例化,并且一切正常。
我调查了Android Profiler,几次导入和导出后的内存使用量介于90 MB到130 MB之间,与之前我没有关闭数据库时一样,因此我认为我没有遇到任何麻烦内存泄漏或累积Room Database实例的情况。
我还应该检查什么?
在Java中,只需在onDestroy回调中将其关闭:
1 2 3 4 5 6 7 8 9 10 11 | @Override protected void onDestroy() { super.onDestroy(); Log.d(TAG,"onDestroy: --------"); if(roomDatabase!=null){ if(roomDatabase.isOpen()) { roomDatabase.close(); } roomDatabase=null; } } |