Android恢复加密数据库后出现room_table_modification_log表不存在错误
问题描述
基于SQLCipher实现SQLite数据库,恢复数据库后执行任何操作都会报错:
Cannot run invalidation tracker. Is the db closed? android.database.sqlite.SQLiteException: no such table: room_table_modification_log: , while compiling: SELECT * FROM room_table_modification_log WHERE invalidated = 1;
只有从最近应用列表关闭再重新打开应用,才不会报错。
相关代码
数据库初始化代码
fun initialize(passphrase: String): Boolean { this.passphrase = SQLiteDatabase.getBytes(passphrase.toCharArray()) this.factory = SupportFactory(this.passphrase, null, false) return try { db = Room.databaseBuilder( context.applicationContext, MyDatabase::class.java, DATABASE_NAME ) .openHelperFactory(factory) .build() db.query("SELECT 1", emptyArray()) true } catch (e: Exception) { false } }
恢复数据库方法
fun restoreDatabase(backupFilePath: Uri, passphrase: String = "a"): Boolean { return try { db.close() val dbFile = context.getDatabasePath(DATABASE_NAME) val file = createTempDatabaseFile(backupFilePath) ?: return false file.copyTo(dbFile, overwrite = true) return initialize("a") } catch (e: Exception) { e.printStackTrace() false } }
补充说明
- 不关闭数据库的话,数据库内容不会更新;
- 仅从最近应用列表关闭再重启才有效,重启Activity无效:
val intent = Intent(context, MainActivity::class.java) (context as MainActivity).finish() context.startActivity(intent)
使用的库版本
- room = "2.6.0-alpha01"
- sqlcipher = "4.5.4"
解决方案
问题核心是Room的InvalidationTracker未被正确重置——即使关闭并重新初始化数据库,Room内部的缓存或静态引用仍持有旧数据库状态,导致查询不存在的room_table_modification_log表(该表是Room跟踪数据变化的内部表,备份库中可能没有,或旧的InvalidationTracker仍指向旧实例)。
具体修复方案
- 彻底清除旧数据库引用
在关闭数据库后,手动置空所有相关引用,让GC回收旧实例,避免残留状态:
fun restoreDatabase(backupFilePath: Uri, passphrase: String = "a"): Boolean { return try { db?.close() // 置空引用,触发GC回收旧实例 db = null factory = null val dbFile = context.getDatabasePath(DATABASE_NAME) // 确保旧数据库文件完全删除后再复制 if (dbFile.exists()) { dbFile.delete() } val file = createTempDatabaseFile(backupFilePath) ?: return false file.copyTo(dbFile, overwrite = true) return initialize("a") } catch (e: Exception) { e.printStackTrace() false } }
- 禁用InvalidationTracker(临时方案)
若上述方法无效,可在初始化时禁用InvalidationTracker,避免其查询内部表:
db = Room.databaseBuilder( context.applicationContext, MyDatabase::class.java, DATABASE_NAME ) .openHelperFactory(factory) // 禁用InvalidationTracker .disableInvalidationTracker() .build()
注意:此方法会导致Room的LiveData等依赖数据变化通知的功能失效,仅适用于不需要实时数据更新的场景。
- 使用Room官方备份恢复API(推荐)
Room 2.4+提供了官方备份恢复API,可自动处理内部表结构和状态同步,避免手动操作文件带来的问题:
// 备份逻辑 db.openHelper.writableDatabase.use { db -> val backupFile = File(context.filesDir, "backup.db") db.backup(backupFile.absolutePath) } // 恢复逻辑 db.close() db = null val backupFile = File(context.filesDir, "backup.db") db = Room.databaseBuilder( context.applicationContext, MyDatabase::class.java, DATABASE_NAME ) .openHelperFactory(factory) .createFromFile(backupFile) .build()
- 重启应用进程(终极方案)
若以上方法均无效,直接重启进程可彻底清除内存中所有状态,达到和从最近列表关闭再打开相同的效果:
fun restartApp() { val intent = context.packageManager.getLaunchIntentForPackage(context.packageName) intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) context.startActivity(intent) Runtime.getRuntime().exit(0) }
在恢复数据库后调用此方法即可。
为什么重启Activity无效?
Room的数据库实例绑定在Application上下文而非Activity上下文,重启Activity不会销毁Application级别的数据库引用和缓存,只有重启进程才能彻底清除。
内容的提问来源于stack exchange,提问作者Captain Jacky
相关产品推荐
相关产品推荐

