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 來處理并發的情況
現在你可以線程安全地使用你的數據庫連接了。