在 ContentProvider 構造函數或查詢方法中調用 SQLiteDatabase.openDatabase() 直接打開數據庫,拋出 SQLiteException: database is locked,堆棧顯示 dbopen 失敗。
SQLiteDatabase.openDatabase() 是底層 API,不具備連接池或單例機制。多次調用會嘗試獲取文件鎖,導致「database is locked」。close() 即嘗試再次打開。SQLiteOpenHelper 是 Android 官方推薦的數據庫管理類,內部實現了:
SQLiteDatabase 實例getReadableDatabase()/getWritableDatabase()onUpgrade()public class MyDatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "my_app.db";
private static final int DATABASE_VERSION = 1;
private static MyDatabaseHelper sInstance;
private static final Object LOCK = new Object();
// 單例獲取
public static MyDatabaseHelper getInstance(Context context) {
synchronized (LOCK) {
if (sInstance == null) {
sInstance = new MyDatabaseHelper(context.getApplicationContext());
}
return sInstance;
}
}
private MyDatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS SearchTable (Owner INTEGER PRIMARY KEY, Generations INTEGER DEFAULT 0)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// 遷移邏輯
}
}
// 使用方式(任何組件中)
SQLiteDatabase db = MyDatabaseHelper.getInstance(context).getWritableDatabase();
db.insert(...);
db.query(...);
// 無需手動 close,由 Helper 管理
public class MyContentProvider extends ContentProvider {
private MyDatabaseHelper mDbHelper;
@Override
public boolean onCreate() {
// 使用 ApplicationContext 避免內存洩漏
mDbHelper = MyDatabaseHelper.getInstance(getContext().getApplicationContext());
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = mDbHelper.getReadableDatabase();
// 使用 SQLiteQueryBuilder 構建查詢
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables("SearchTable");
Cursor cursor = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder);
// 重要:設置通知 URI,數據變更時自動通知 Cursor
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
// insert/update/delete 同理使用 mDbHelper.getWritableDatabase()
}
| 錯誤做法 | 正確做法 |
|---|---|
| 多處 new SQLiteOpenHelper() | 單例模式共享同一 Helper 實例 |
| 直接 openDatabase() | 使用 helper.getWritableDatabase() |
| 手動 db.close() | 由 Helper 引用計數自動管理 |
| 在 UI 線程執行耗時操作 | 使用 AsyncTask/Coroutines/RxJava 異步執行 |
| 跨進程共享無 WAL 模式 | 啟用 db.enableWriteAheadLogging() |
@Override
public void onConfigure(SQLiteDatabase db) {
super.onConfigure(db);
// 啟用 Write-Ahead Logging,讀寫不再互斥,大幅提升並發
db.enableWriteAheadLogging();
}
適用於多進程(如 ContentProvider + Service)並發讀寫場景。