日期:2014-05-16  浏览次数:20322 次

【翻译】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 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