Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android多線程下安全訪問數據庫

Android多線程下安全訪問數據庫

編輯:關於Android編程

為了記錄如何線程安全地訪問你的Android數據庫實例,我寫下了這篇小小札記。文章中引用的項目代碼請點擊這裡
假設你已編寫了自己的 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 對象時,實際上也是在新建一個數據庫連接。如果你嘗試通過多個連接同時對數據庫進行寫數據操作,其一定會失敗。

為確保我們能在多線程中安全地操作數據庫,我們需要保證只有一個數據庫連接被占用。

我們先編寫一個負責管理單個 SQLiteOpenHelper 對象的單例 DatabaseManager 。

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 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() 的調用就會取得一樣的 SQLiteDatabase 對象實例。之後的事情就是,當 Thread1 嘗試管理數據庫連接時,Thread2 卻仍然在使用該數據庫連接。這也就是導致 IllegalStateException 崩毀的原因。

因此我們只能在確保數據庫沒有再被占用的情況下,才去關閉它。在 stackoveflow 上有一些討論推薦“永不關閉”你的 SQLiteDatabase 。 如果你這樣做,你的logcat將會出現以下的信息,因此我不認為這是一個好主意。
Leak foundCaused 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,代表我們需要打開一個新的數據庫連接,否則,數據庫連接已經存在。

在方法 closeDatabase() 中,情況也一樣。每次我們調用 closeDatabase() 方法,計數器都會遞減,直到計數為0,我們就需要關閉數據庫連接了。

提示: 你應該使用 AtomicInteger 來處理並發的情況

現在你可以線程安全地使用你的數據庫連接了。



原文: https://github.com/dmytrodanylyk/dmytrodanylyk/blob/gh-pages/articles/Concurrent%20Database%20Access.md
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved