? ? ? 为了记录如何线程安全地访问你的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 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()?的调用就会取得一样的?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(mOpenC