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

Android Room出现SQLiteReadOnlyDatabaseException异常如何解决

异常捕获实现

你当前无法捕获异常的核心原因是:updateViewLog方法的try-catch包裹在协程launch调用外层,协程异步执行过程中抛出的异常不会传递到外层;getViewLog作为挂起函数虽然加了try-catch,但未显式捕获特定的只读异常,同时初始化阶段的异常也没有兜底处理。具体捕获方案如下:

1. 异步协程操作捕获(对应updateViewLog方法)

将try-catch挪到launch代码块内部,包裹所有数据库操作逻辑:

fun updateViewLog(viewLogModel: ViewLogModel) {
    coroutineScope.launch {
        try {
            val db = MyAppDatabase.getAppDataBase(context)
            db.viewLogDao().insertOrUpdateViewLog(viewLogModel)
        } catch (e: SQLiteReadOnlyDatabaseException) {
            // 捕获只读异常,触发重置逻辑
            resetDatabase(context)
        } catch (e: Exception) {
            // 其他异常兜底处理
        }
    }
}

2. 挂起函数操作捕获(对应getViewLog方法)

显式添加SQLiteReadOnlyDatabaseException捕获分支,优先处理只读异常:

suspend fun getViewLog(memberId: Int): JSONArray {
    return try {
        val jsonArray = JSONArray()
        val db = MyAppDatabase.getAppDataBase(context) ?: return JSONArray()
        val viewLog = db.viewLogDao().getViewLog(memberId)

        for (view in viewLog) {
            val jsonObject = JSONObject()
            jsonObject.put("mid", view.mid)
            jsonObject.put("uts", view.uts)
            jsonArray.put(jsonObject)
        }

        db.viewLogDao().deleteViewLog(memberId, Utilities.getUtsYesterday().toFloat())
        jsonArray
    } catch (e: SQLiteReadOnlyDatabaseException) {
        resetDatabase(context)
        JSONArray()
    } catch (e: Exception) {
        JSONArray()
    }
}

3. 初始化阶段异常捕获

在单例初始化方法中添加try-catch,捕获数据库创建、asset复制阶段的异常,失败后自动重试一次:

fun getAppDataBase(context: Context): MyAppDatabase? {
    return INSTANCE ?: synchronized(this) {
        INSTANCE ?: try {
            Room.databaseBuilder(context.applicationContext, MyAppDatabase::class.java, "MyAppDatabase")
                .createFromAsset("myapp.db")
                .setJournalMode(RoomDatabase.JournalMode.TRUNCATE)
                .fallbackToDestructiveMigration()
                .build().also {
                    INSTANCE = it
                }
        } catch (e: Exception) {
            // 初始化失败,删除损坏的数据库文件重试
            context.deleteDatabase("MyAppDatabase")
            try {
                Room.databaseBuilder(context.applicationContext, MyAppDatabase::class.java, "MyAppDatabase")
                    .createFromAsset("myapp.db")
                    .setJournalMode(RoomDatabase.JournalMode.TRUNCATE)
                    .fallbackToDestructiveMigration()
                    .build().also {
                        INSTANCE = it
                    }
            } catch (e: Exception) {
                null
            }
        }
    }
}

数据库可读写保障方案

报错码1032 SQLITE_READONLY_DBMOVED的根因通常是:数据库文件被意外删除/移动、asset数据库第一次复制不完整、数据库实例被异常销毁后仍有操作在执行、系统权限异常。具体优化方案如下:

  • 禁用非必要的销毁逻辑:destroyDataBase方法不要在业务运行过程中调用,避免单例被置空后,正在执行的数据库操作连接失效。
  • 异常自动重置机制:捕获到只读异常后,主动关闭旧实例、删除损坏的数据库文件、重置单例,下次操作会自动从asset重新复制完整的数据库文件,实现自恢复:
    private fun resetDatabase(context: Context) {
        synchronized(MyAppDatabase::class.java) {
            try {
                MyAppDatabase.INSTANCE?.close()
                context.deleteDatabase("MyAppDatabase")
                MyAppDatabase.INSTANCE = null
            } catch (e: Exception) {
                // 忽略清理异常
            }
        }
    }
    
  • 一致性优化:你的Dao中deleteViewLog语句写的表名是viewlog,实体类定义的表名是viewLog,虽然SQLite默认不区分大小写,但建议统一避免极端场景下的表识别错误。
  • 可选优化:如果没有特殊需求可以移除setJournalMode(RoomDatabase.JournalMode.TRUNCATE)配置,使用默认的WAL日志模式,读写性能和稳定性更优。

内容的提问来源于stack exchange,提问作者Mike Walker

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 23:06:06