解決Android中的SQLite數據庫并發訪問

jopen 9年前發布 | 127K 次閱讀 SQLite Android開發 移動開發

我曾經寫過一篇很簡短的文章,闡述了線程安全的訪問android sqlite數據庫。樣例程序可以在這里獲取到。


假設你已經有一個自己的SQLiteOpenHelper。
public class DatabaseHelper extends SQLiteOpenHelper { ... }
現在你想要在不同的線程中向數據庫寫數據
 // Thread 1
 Context context = getApplicationContext();
 DatabaseHelper helper = new DatabaseHelper(context);
 SQLiteDatabase database = helper.getWritableDatabase();
 database.insert(…);
 database.close();

 // Thread 2
 Context context = getApplicationContext();
 DatabaseHelper helper = new DatabaseHelper(context);
 SQLiteDatabase database = helper.getWritableDatabase();
 database.insert(…);
 database.close();

在你的logcat中將會得到下面的輸出信息,并且有一個寫操作將不會成功
android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
發生這種情況是因為你每次都創建了一個新的SQLiteOpenHelper,實際上你每次都創建了一個數據庫的連接。如果你在同一時間用不同的數據庫連接來對同一的數據庫進行寫操作的話,那么其中一個會失敗。

在Android平臺上,如果你想在多線程環境下安全的使用數據庫的話,那么你得確保所有的線程使用的都是同一個數據庫連接。

確保只有一個數據庫連接存在,我們可以使用單例模式,代碼如下
 public class DatabaseManager {

    private static DatabaseManager instance;
    private static SQLiteOpenHelper mDatabaseHelper;

    public static synchronized void initialize(Context context, SQLiteOpenHelper helper) {
        if (instance == null) {
            instance = new DatabaseManager();
            mDatabaseHelper = helper;
        }
    }

    public static synchronized DatabaseManager getInstance() {
        if (instance == null) {
            throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
                    " is not initialized, call initialize(..) method first.");
        }

        return instance;
    }

    public synchronized SQLiteDatabase getDatabase() {
        return new mDatabaseHelper.getWritableDatabase();
    }

}

現在我們來看看執行結果
 // In your application class
 DatabaseManager.initializeInstance(getApplicationContext());

 // Thread 1
 DatabaseManager manager = DatabaseManager.getInstance();
 SQLiteDatabase database = manager.getDatabase()
 database.insert(…);
 database.close();

 // Thread 2
 DatabaseManager manager = DatabaseManager.getInstance();
 SQLiteDatabase database = manager.getDatabase()
 database.insert(…);
 database.close();

你的應用將會崩潰,異常信息如下
java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase
因為我們只使用一個數據庫連接,Thread1和Thread2的都是由getDatabase()方法返回的相同連接。發生的什么事呢,在Thread2還在使用數據庫連接時,Thread1可能已經把它給關閉了,那就是為什么你會得到崩潰異常。

我們需要確保在沒有任何一個人在使用數據庫時,才去關閉它。在StackOverflow上推薦的做法是永遠不要關閉數據庫。Android會尊重你這種做法,但會給你如下的提示。所以我一點也不推薦這種做法。
Leak found
Caused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed
下面是一個樣例程序
 public class DatabaseManager {

    private AtomicInteger mOpenCounter = new AtomicInteger();

    private static DatabaseManager instance;
    private static SQLiteOpenHelper mDatabaseHelper;
    private SQLiteDatabase mDatabase;

    public static synchronized void initializeInstance(SQLiteOpenHelper helper) {
        if (instance == null) {
            instance = new DatabaseManager();
            mDatabaseHelper = helper;
        }
    }

    public static synchronized DatabaseManager getInstance() {
        if (instance == null) {
            throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
                    " is not initialized, call initializeInstance(..) method first.");
        }

        return instance;
    }

    public synchronized SQLiteDatabase openDatabase() {
        if(mOpenCounter.incrementAndGet() == 1) {
            // Opening new database
            mDatabase = mDatabaseHelper.getWritableDatabase();
        }
        return mDatabase;
    }

    public synchronized void closeDatabase() {
        if(mOpenCounter.decrementAndGet() == 0) {
            // Closing database
            mDatabase.close();

        }
    }}

使用方式:
SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();
database.insert(...);
// database.close(); Don't close it directly!
DatabaseManager.getInstance().closeDatabase(); // correct way

每當你需要使用數據庫時,你需要使用DatabaseManager的openDatabase()方法來取得數據庫。我們會使用一個引用計數來判斷是否要創建數據庫對象。如果引用計數為1,則需要創建一個數據庫,如果不為1,說明我們已經創建過了。

在closeDatabase()方法中我們同樣通過判斷引用計數的值,如果引用計數降為0,則說明我們需要close數據庫。
 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!