Android多線程下安全訪問數據庫
// 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 new mDatabaseHelper.getWritableDatabase(); }}</pre> <p style="line-height:20px;background-color:#222222;margin-top:0px;font-family:'Helvetica Neue', Helvetica, Arial, sans-serif;white-space:normal;color:#999999;font-size:14px;"> 為了能在多線程中進行寫數據操作,我們得修改一下代碼,具體如下: </p>
// 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();</pre>
然后又導致另個崩毀
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(); } }}</pre> <p style="line-height:20px;background-color:#222222;margin-top:0px;font-family:'Helvetica Neue', Helvetica, Arial, sans-serif;white-space:normal;color:#999999;font-size:14px;"> 然后你可以怎樣子去調用它:</p>
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 來處理并發的情況
現在你可以線程安全地使用你的數據庫連接了。