Room数据库无法持久化数据:Kotlin Jetpack Compose MVVM应用问题
问题复盘
你遇到的情况是:基于Kotlin+Jetpack Compose+MVVM的应用里,Room在应用内读写完全正常,但每次重启后之前的数据全部消失。通过Device File Explorer观察到主库文件my_database.db创建后再也没被修改过,反而db-shm和db-wal文件会随操作频繁更新。已经试过注释fallbackToDestructiveMigration、createFromAsset,手动开关数据库这些操作,但都没用,而且确定没使用内存数据库。
核心原因
Room默认用SQLite的WAL(预写日志)模式,正常情况下WAL日志会自动合并到主数据库文件。但如果数据库连接没被正确关闭,进程退出时WAL文件里的未提交日志会被丢弃,导致重启后数据丢失。另外,单例实现的疏漏、WAL自动合并阈值未触发也可能是诱因。
具体解决步骤
1. 修正数据库连接的生命周期管理
别手动在Activity销毁时关数据库,Room的单例应该由框架管理,但可以在进程退出时确保关闭:
class MyApplication : Application() { override fun onTerminate() { super.onTerminate() MyDatabase.closeDatabase() } }
同时给你的数据库单例加个手动关闭方法:
companion object { @Volatile private var INSTANCE: MyDatabase? = null fun getDatabase(context: Context): MyDatabase { return INSTANCE ?: synchronized(this) { val instance = Room.databaseBuilder( context.applicationContext, MyDatabase::class.java, "my_database.db" ).build() INSTANCE = instance instance } } fun closeDatabase() { INSTANCE?.let { it.close() INSTANCE = null } } }
2. 强制WAL日志合并到主库
可以在每次批量写入后手动触发检查点,确保数据写入主库文件:
// 在DAO操作完成后执行 myDatabase.runInTransaction { myDatabase.openHelper.writableDatabase.rawQuery("PRAGMA wal_checkpoint(FULL)", null).close() }
或者在数据库构建时调整自动检查点的阈值(默认是1000页,调小可以更频繁合并):
Room.databaseBuilder(...) .addCallback(object : RoomDatabase.Callback() { override fun onOpen(db: SupportSQLiteDatabase) { super.onOpen(db) db.execSQL("PRAGMA wal_autocheckpoint=500") } }) .build()
3. 确认单例模式没有漏洞
虽然你的单例代码看起来没问题,但可以再加一层检查,避免多实例冲突:
fun getDatabase(context: Context): MyDatabase { val tempInstance = INSTANCE if (tempInstance != null) { return tempInstance } synchronized(this) { val instance = Room.databaseBuilder( context.applicationContext, MyDatabase::class.java, "my_database.db" ).build() INSTANCE = instance return instance } }
4. 验证数据库存储路径
先确认数据库文件是不是存在应用的私有目录里,避免被系统清理:
// 在任意能拿到Context的地方打印路径 val dbPath = MyDatabase.getDatabase(context).openHelper.writableDatabase.path Log.d("DB_PATH", dbPath)
正常路径应该是/data/data/[你的包名]/databases/my_database.db,如果路径不对,检查Context是不是用了applicationContext(你当前代码是对的,但再确认一次)。
5. 临时禁用WAL排查问题(不推荐长期用)
如果以上方法都没用,可以临时改成传统的TRUNCATE日志模式,验证是不是WAL的问题:
Room.databaseBuilder(...) .setJournalMode(RoomDatabase.JournalMode.TRUNCATE) .build()
注意:这个模式会降低写入性能,只是用来排查,别线上用。
验证方式
- 写入数据后,要么等几分钟,要么手动触发检查点,然后看Device File Explorer里
my_database.db的修改时间有没有更新 - 重启应用后直接查数据,看是否保留
内容的提问来源于stack exchange,提问作者Austin Ferrier

