推荐用于在Android中写入SQLite数据库的设计模式
伙计们,
我正在寻找一种设计模式,使UI线程能够与可能批量插入(花费10秒),快速插入和读取的客户端SQLite数据库进行交互,并且不会阻塞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;
}
}
}
强调:
这是否表示将潜在的大量数据写入SQLite数据库同时最大限度地减少对UI线程的影响的Android最佳实践? 上面看到的伪代码是否存在明显的同步问题?
更新
我的代码中有一个重要的错误,如下所示:
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
我不能说太多关于同步/死锁部分,这将非常依赖于其他代码。 由于DataManager
类并未真正与UI交互,因此您可能需要使用服务( IntentService
)而不是AsyncTask
。 完成同步后,您可以显示通知。 如果您不调用UI代码,则不需要onPostExecute()
。
您可能需要考虑SDK中的这些信息(http://developer.android.com/reference/android/os/AsyncTask.html)
首次引入时,AsyncTasks在单个后台线程上被串行执行。 从DONUT开始,将其更改为允许多个任务并行操作的线程池。 从HONEYCOMB开始,任务在单个线程上执行,以避免并行执行导致的常见应用程序错误。
如果您真的想要并行执行,可以使用THREAD_POOL_EXECUTOR调用executeOnExecutor(java.util.concurrent.Executor,Object [])。
仅供参考,SQLite上运行的每个SQL语句都在一个事务下运行,即使您没有指定一个SQL语句。
检查下面的线程,如果你在SQLite中进行批量插入 :
上一篇: Recommended design pattern for writing to SQLite database in Android