Android Room报错‘Cannot run invalidation tracker. Is the db closed?’,greenDao数据库迁移至Room失败求助
嗨,我之前也踩过Room跨数据库迁移的坑,太懂你现在卡壳的烦躁了!先捋清楚:你是想在Room数据库首次创建时,把GreenDao生成的旧SQLite数据库数据导进来,用了SQLite的ATTACH DATABASE机制,结果触发了「Cannot run invalidation tracker. Is the db closed?」这个报错对吧?
这个问题本质上是Room的内部连接管理和我们手动的跨库操作冲突了,给你几个亲测有效的解决思路:
别在onCreate回调里直接搞复杂操作
Room的onCreate回调里的SupportSQLiteDatabase对象是Room初始化过程中的临时连接,Room后续还要靠它做内部的失效跟踪、状态同步,如果你在这里硬塞ATTACH这种跨库操作,很容易打乱它的内部状态,直接触发那个报错。建议把迁移逻辑挪到Room完全初始化之后再执行。ATTACH DATABASE的正确打开方式
如果你一定要用ATTACH来迁移,这几个细节千万别漏:- 旧数据库路径必须用绝对路径,SQLite的ATTACH认完整路径,比如
/data/data/com.yourapp/databases/old_greendao_db.db,别用相对路径! - 所有操作必须包在事务里,而且操作完一定要DETACH旧库,不然连接挂着会干扰Room的跟踪器。代码大概是这样:
val oldDbPath = "/data/data/com.yourapp/databases/old_greendao_db.db" db.beginTransaction() try { // 挂载旧数据库 db.execSQL("ATTACH DATABASE '$oldDbPath' AS old_db") // 数据拷贝,这里要注意表名、列名和Room的实体类完全匹配! db.execSQL("INSERT INTO user (id, name, age) SELECT id, name, age FROM old_db.user") // 标记事务成功 db.setTransactionSuccessful() } catch (e: Exception) { // 出问题就打日志排查 e.printStackTrace() } finally { // 一定要卸载旧库 db.execSQL("DETACH DATABASE old_db") db.endTransaction() }
- 旧数据库路径必须用绝对路径,SQLite的ATTACH认完整路径,比如
绝对别手动关闭onCreate里的db对象
我之前傻呵呵地在迁移完加了db.close(),直接触发了这个报错!Room的数据库连接是它自己管理的,你手动关闭了,后续Room的失效跟踪器要干活的时候发现数据库关了,直接就炸了。换个时机执行迁移逻辑
可以在Room数据库实例创建完成后,在子线程里执行迁移,比如:// 先创建Room实例 val appDatabase = Room.databaseBuilder( context, AppDatabase::class.java, "new_room_db" ).addCallback(object : RoomDatabase.Callback() { override fun onCreate(db: SupportSQLiteDatabase) { // 这里只做个标记,告诉后续逻辑需要迁移 needMigrate = true } }).build() // 之后在子线程里执行迁移(比如用协程) if (needMigrate) { CoroutineScope(Dispatchers.IO).launch { val db = appDatabase.openHelper.writableDatabase // 这里执行上面写的ATTACH+数据拷贝逻辑 } }
另外还有个小坑要注意:GreenDao生成的表名、列名可能和Room的实体类对应的名称不一样,比如GreenDao可能用user_info,Room默认用实体类名User转成的user,所以写拷贝SQL的时候一定要核对清楚表和列的名称,别因为这个导致SQL执行失败,间接干扰数据库状态。
内容来源于stack exchange

