推荐用于在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;
    }
  }
}

强调:

  • 首先:所有的公开写操作(writeMessage)都是通过一个AsyncTask进行的,而不是在主线程中
  • 下一步:所有写入操作都将同步并包装在BEGIN TRANSACTIONS中
  • 下一步:读取操作是非同步的,因为它们在写入期间不需要阻塞
  • 最后:读取操作的结果缓存在onPostExecute的主线程上
  • 这是否表示将潜在的大量数据写入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中进行批量插入

  • Android数据库交易
  • SQLite批量插入
  • 链接地址: http://www.djcxy.com/p/11021.html

    上一篇: Recommended design pattern for writing to SQLite database in Android

    下一篇: FallbackResource on Apache2 (2.2.22 (Ubuntu))