天天看點

一個SQLiteReadOnlyDatabaseException的問題

最近測試同僚上報了随機的一個crash問題,看日志是SQLiteReadOnlyDatabaseException的問題,發生在update資料庫的時候。我自己寫的ContentProvider代碼就是在程式目錄下放置資料,是以不是網上常見的缺少寫sd卡權限的原因。具體分析要看日志:

06-08 10:31:46.358  1913  1913 E AndroidRuntime: android.database.sqlite.SQLiteReadOnlyDatabaseException: attempt to write a readonly database (code 1032)
06-08 10:31:46.358  1913  1913 E AndroidRuntime: 	at android.database.sqlite.SQLiteConnection.nativeExecuteForChangedRowCount(Native Method)
06-08 10:31:46.358  1913  1913 E AndroidRuntime: 	at android.database.sqlite.SQLiteConnection.executeForChangedRowCount(SQLiteConnection.java:741)
06-08 10:31:46.358  1913  1913 E AndroidRuntime: 	at android.database.sqlite.SQLiteSession.executeForChangedRowCount(SQLiteSession.java:754)
06-08 10:31:46.358  1913  1913 E AndroidRuntime: 	at android.database.sqlite.SQLiteStatement.executeUpdateDelete(SQLiteStatement.java:64)
06-08 10:31:46.358  1913  1913 E AndroidRuntime: 	at android.database.sqlite.SQLiteDatabase.updateWithOnConflict(SQLiteDatabase.java:1606)
06-08 10:31:46.358  1913  1913 E AndroidRuntime: 	at android.database.sqlite.SQLiteDatabase.update(SQLiteDatabase.java:1552)
06-08 10:31:46.358  1913  1913 E AndroidRuntime: 	at com.hb.provider.HbSettingProvider.update(HbSettingProvider.java:90)
06-08 10:31:46.358  1913  1913 E AndroidRuntime: 	at android.content.ContentProvider$Transport.update(ContentProvider.java:366)
06-08 10:31:46.358  1913  1913 E AndroidRuntime: 	at android.content.ContentResolver.update(ContentResolver.java:1411)
           

日志中有個code 1032,這個會提供更具體的資訊,網絡搜尋到資訊如下:

SQL return 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.

看出這個真正的原因是實體資料庫檔案已被移除,和SQLiteReadOnlyDatabaseException的字面意思有差距,并不是資料庫隻讀。

參見SQLiteOpenHelper源碼

frameworks/base/core/java/android/database/sqlite/SQLiteOpenHelper.java

public SQLiteDatabase getWritableDatabase() {
        synchronized (this) {
            return getDatabaseLocked(true);
        }
    }
           
private SQLiteDatabase getDatabaseLocked(boolean writable) {
        if (mDatabase != null) {
            if (!mDatabase.isOpen()) {
                // Darn!  The user closed the database by calling mDatabase.close().
                mDatabase = null;
            } else if (!writable || !mDatabase.isReadOnly()) {
                // The database is already open for business.
                return mDatabase;
            }
        }
        ...
    }
           

可以看出如果mDataBase已經被打開過的話,會直接傳回mDataBase對象,并沒有判斷資料庫檔案已經被删除的情況。

會發生此種現象的原因是ContentProvider運作在常駐程序中,有過一次資料庫操作後mDataBase對象就會生成,後續如果有清除該程式資料的操作就會導緻這個問題。至于為啥會清除資料那就是另外的問題了。