伙计们,
我正在寻找一种设计模式,使 UI 线程能够与客户端 SQLite 数据库进行交互,该数据库可能具有批量插入(需要 10 秒)、快速插入和读取,并且不会阻塞 UI 线程。
我想知道我是否为此使用了最佳设计模式,因为我最近一直在调试死锁和同步问题,而且我对我的最终产品不是 100% 有信心。
现在,所有数据库访问都通过单例类成为瓶颈。这是显示我如何在我的单例 DataManager 中进行写入的伪代码:
public class DataManager {
private SQLiteDatabase mDb;
private ArrayList<Message> mCachedMessages;
public ArrayList<Message> readMessages() {
return mCachedMessages;
}
public void writeMessage(Message m) {
new WriteMessageAsyncTask().execute(m);
}
protected synchronized void dbWriteMessage(Message m) {
this.mDb.replace(MESSAGE_TABLE_NAME, null, m.toContentValues());
}
protected ArrayList<Message> dbReadMessages() {
// SQLite query for messages
}
private class WriteMessageAsyncTask extends AsyncTask<Message, Void, ArrayList<Messages>> {
protected Void doInBackground(Message... args) {
DataManager.this.mDb.execSQL("BEGIN TRANSACTION;");
DataManager.this.dbWriteMessage(args[0]);
// More possibly expensive DB writes
DataManager.this.mDb.execSQL("COMMIT TRANSACTION;");
ArrayList<Messages> newMessages = DataManager.this.dbReadMessages();
return newMessages;
}
protected void onPostExecute(ArrayList<Message> newMessages) {
DataManager.this.mCachedMessages = newMessages;
}
}
}
强调:
- 第一:所有公共写操作(writeMessage)都通过 AsyncTask 发生,从不在主线程上
- 下一篇:所有写操作都同步并包裹在BEGIN TRANSACTIONS中
- Next: 读操作是非同步的,因为它们在写期间不需要阻塞
- 最后:读操作的结果缓存在onPostExecute中的主线程上
这是否代表了将潜在的大量数据写入 SQLite 数据库同时最小化对 UI 线程的影响的 Android 最佳实践?您在上面看到的伪代码是否存在任何明显的同步问题?
更新
我上面的代码有一个重大的bug,具体如下:
DataManager.this.mDb.execSQL("BEGIN TRANSACTION;");
该行获取数据库上的锁。但是,它是一个 DEFERRED 锁,因此在发生写入之前,其他客户端既可以读取也可以写入。
DataManager.this.dbWriteMessage(args[0]);
该行实际上修改了数据库。此时,锁是一个保留锁,所以没有其他客户端可以写。
请注意,在第一次 dbWriteMessage 调用之后,可能会有更昂贵的数据库写入。假设每个写操作都发生在受保护的同步方法中。这意味着在 DataManager 上获得了一个锁,写入发生,并且锁被释放。如果 WriteAsyncMessageTask 是唯一的写入器,这很好。
现在让我们假设还有一些其他任务也执行写入操作,但不使用事务(因为它是快速写入)。下面是它的样子:
private class WriteSingleMessageAsyncTask extends AsyncTask<Message, Void, Message> {
protected Message doInBackground(Message... args) {
DataManager.this.dbWriteMessage(args[0]);
return args[0];
}
protected void onPostExecute(Message newMessages) {
if (DataManager.this.mCachedMessages != null)
DataManager.this.mCachedMessages.add(newMessages);
}
}
在这种情况下,如果 WriteSingleMessageAsyncTask 与 WriteMessageAsyncTask 同时执行,并且 WriteMessageAsyncTask 已经执行了至少一次写入,则 WriteSingleMessageAsyncTask 可能调用 dbWriteMessage,获取 DataManager 上的锁,但由于以下原因而被阻止完成其写入保留锁。WriteMessageAsyncTask 反复获取和放弃DataManager 上的锁,这是一个问题。
要点:将事务和单例对象级锁定结合起来可能会导致死锁。确保在开始事务之前拥有对象级锁。
对我原来的 WriteMessageAsyncTask 类的修复:
synchronized(DataManager.this) {
DataManager.this.mDb.execSQL("BEGIN TRANSACTION;");
DataManager.this.dbWriteMessage(args[0]);
// More possibly expensive DB writes
DataManager.this.mDb.execSQL("COMMIT TRANSACTION;");
}
更新 2
观看来自 Google I/O 2012 的视频:http: //youtu.be/gbQb1PVjfqM?t= 19m13s
它建议使用内置独占事务然后使用yieldIfContendedSafely的设计模式