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
相关产品推荐
相关产品推荐

