关于android:如何正确关闭并重新打开Room Database

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