首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

Android 開發:解決 SQLiteException: database is locked 異常的根因與最佳實踐

     編輯:程式設計綜合問答

異常現象

在 ContentProvider 構造函數或查詢方法中調用 SQLiteDatabase.openDatabase() 直接打開數據庫,拋出 SQLiteException: database is locked,堆棧顯示 dbopen 失敗。

根本原因

  • 重複打開同一數據庫文件:SQLiteDatabase.openDatabase() 是底層 API,不具備連接池或單例機制。多次調用會嘗試獲取文件鎖,導致「database is locked」。
  • 跨組件共享無協調:多個 ContentProvider、Service、Activity 獨立打開同一文件,無鎖協調。
  • 未正確關閉:前一次打開未調用 close() 即嘗試再次打開。

標準解決方案:使用 SQLiteOpenHelper 單例模式

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 管理

ContentProvider 中的正確用法

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()

進階:WAL 模式提升並發性能

@Override
public void onConfigure(SQLiteDatabase db) {
    super.onConfigure(db);
    // 啟用 Write-Ahead Logging,讀寫不再互斥,大幅提升並發
    db.enableWriteAheadLogging();
}

適用於多進程(如 ContentProvider + Service)並發讀寫場景。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved