You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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仍指向旧实例)。

具体修复方案

  1. 彻底清除旧数据库引用
    在关闭数据库后,手动置空所有相关引用,让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
    }
}
  1. 禁用InvalidationTracker(临时方案)
    若上述方法无效,可在初始化时禁用InvalidationTracker,避免其查询内部表:
db = Room.databaseBuilder(
    context.applicationContext,
    MyDatabase::class.java,
    DATABASE_NAME
)
    .openHelperFactory(factory)
    // 禁用InvalidationTracker
    .disableInvalidationTracker()
    .build()

注意:此方法会导致Room的LiveData等依赖数据变化通知的功能失效,仅适用于不需要实时数据更新的场景。

  1. 使用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()
  1. 重启应用进程(终极方案)
    若以上方法均无效,直接重启进程可彻底清除内存中所有状态,达到和从最近列表关闭再打开相同的效果:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 09:47:32