Android 房间 SQLiteReadOnlyDatabaseException

Android Room SQLiteReadOnlyDatabaseException

我已将我的应用程序转换为使用 Android Room for SQLite DB。我的实现在不同设备上出现了一些崩溃。

Fatal Exception: android.database.sqlite.SQLiteReadOnlyDatabaseException: attempt to write a readonly database (code 1032 SQLITE_READONLY_DBMOVED)
   at android.database.sqlite.SQLiteConnection.nativeExecute(SQLiteConnection.java)
   at android.database.sqlite.SQLiteConnection.execute(SQLiteConnection.java:707)
   at android.database.sqlite.SQLiteConnection.setLocaleFromConfiguration(SQLiteConnection.java:473)
   at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:261)
   at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:205)
   at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:505)
   at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:206)
   at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:198)
   at android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:918)
   at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:898)
   at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:762)
   at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:751)
   at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:373)
   at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:316)
   at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.getWritableSupportDatabase(FrameworkSQLiteOpenHelper.java:145)
   at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper.getWritableDatabase(FrameworkSQLiteOpenHelper.java:106)
   at androidx.room.SQLiteCopyOpenHelper.getWritableDatabase(SQLiteCopyOpenHelper.java:97)
   at androidx.room.RoomDatabase.internalBeginTransaction(RoomDatabase.java:482)
   at androidx.room.RoomDatabase.beginTransaction(RoomDatabase.java:471)
   at com.luzeon.MyApp.sqlite.ViewLogDao_Impl.call(ViewLogDao_Impl.java:94)
   at com.luzeon.MyApp.sqlite.ViewLogDao_Impl.call(ViewLogDao_Impl.java:91)
   at androidx.room.CoroutinesRoom$Companion$execute.invokeSuspend(CoroutinesRoom.kt:61)
   at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
   at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
   at androidx.room.TransactionExecutor.run(TransactionExecutor.java:47)
   at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
   at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
   at java.lang.Thread.run(Thread.java:923)

我已经创建了应用 Room DB

@Database(entities = [ViewLogModel::class], version = 4)
abstract class MyAppDatabase : RoomDatabase() {
abstract fun viewLogDao(): ViewLogDao

companion object {
    // For Singleton Instance
    @Volatile
    private var INSTANCE: MyAppDatabase? = null

    fun getAppDataBase(context: Context): MyAppDatabase {
        return INSTANCE ?: synchronized(this) {
            INSTANCE ?: Room.databaseBuilder(context.applicationContext, MyAppDatabase::class.java, "MyAppDatabase")
                .createFromAsset(“myapp.db")
                .setJournalMode(RoomDatabase.JournalMode.TRUNCATE) // disable WAL
                .fallbackToDestructiveMigration()
                .build()
        }
    }

    fun destroyDataBase(){
        INSTANCE = null
    }
}

}

并且有一个数据库助手class

class MyAppDatabaseHelper(private val context: Context, private val coroutineScope: CoroutineScope) {

fun updateViewLog(viewLogModel: ViewLogModel) {

    try {
        // get the database
        val db = MyAppDatabase.getAppDataBase(context)

        coroutineScope.launch {
            // store in db
            db.viewLogDao().insertOrUpdateViewLog(viewLogModel)

        }
    } catch (e: Exception) {}
}

suspend fun getViewLog(memberId: Int): JSONArray {
    try {
        val jsonArray = JSONArray()

        // get the database
        val db = MyAppDatabase.getAppDataBase(context)

        val viewLog = db.viewLogDao().getViewLog(memberId)

        for (view in viewLog) {
            // create the object
            val jsonObject = JSONObject()
            try {
                jsonObject.put("mid", view.mid)
            } catch (e: JSONException) {
            }
            try {
                jsonObject.put("uts", view.uts)
            } catch (e: JSONException) {
            }
            jsonArray.put(jsonObject)
        }

        // clear log (current user records or records older than 24hrs)
        db.viewLogDao().deleteViewLog(memberId, Utilities.getUtsYesterday().toFloat())

        // return the array
        return jsonArray
    } catch (e: Exception) {

        return JSONArray()
    }

}

}

使用 ViewLogDao

@Dao
interface ViewLogDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertOrUpdateViewLog(viewLog: ViewLogModel)

@Update
suspend fun updateViewLog(viewLog: ViewLogModel)

@Delete
suspend fun deleteAllViewLog(viewLog: ViewLogModel)

@Query("DELETE FROM viewlog WHERE VID= :vid OR UTS < :uts")
suspend fun deleteViewLog(vid: Int, uts: Float)

@Query("SELECT * FROM viewLog")
suspend fun getAll(): List<ViewLogModel>

@Query("SELECT * FROM viewLog WHERE vid = :vid")
suspend fun getViewLog(vid: Int): List<ViewLogModel>

}

和 ViewLogModel

@Entity(tableName = "viewLog")
data class ViewLogModel(
    @ColumnInfo(name = "VID") val vid: Int,
    @ColumnInfo(name = "UTS") val uts: Float,
    @ColumnInfo(name = "MID") @PrimaryKey val mid: Int)

我无法找到如何在数据库为只读时捕获 SQLiteReadOnlyDatabaseException 的罕见情况。或者有没有办法确保 ROOM Db 是 read/write?

I have not been able to find how to catch the SQLiteReadOnlyDatabaseException in the rare occurrences when the DB is read only. Or is there a way to ensure the ROOM Db is read/write?

留言code 1032 SQLITE_READONLY_DBMOVED:-

The SQLITE_READONLY_DBMOVED error code is an extended error code for SQLITE_READONLY. The SQLITE_READONLY_DBMOVED error code indicates that a database cannot be modified because the database file has been moved since it was opened, and so any attempt to modify the database might result in database corruption if the processes crashes because the rollback journal would not be correctly named.

如果消息可信,则数据库已 moved/renamed。从消息中可以看出,(正在处理的两个数据库之一)数据库在打开时正在重命名。

在日志中,许多条目都是相似的,因此看起来正在管理两个数据库,即这是从资产创建数据库的阶段。

这很可能是 createFromAsset 处理的问题,据我所知,这不一定是坚如磐石的。例如目前有issues with the prePackagedDatabaseCallback.

因此,通过使用 createFromAsset,您除了提出问题之外别无他法。

我建议绕过这个问题,在将控制权交给 Room 之前自己预先复制资产。

  • 进行复制不需要像打开文件一样打开数据库。

另一种选择是查看是否只使用 WAL 模式来解决这个问题。由于您正在禁用 WAL 模式,所以我猜您不希望这样做(因此为什么建议最后一个)。

  • 这不仅需要禁用 WAL 模式,而且还需要在分发之前将资产设置为 WAL 模式。