用于在Android中写入SQLite数据库的推荐设计模式

esi*_*ver 7 sqlite multithreading android synchronization

伙计们,

我正在寻找一种设计模式,使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;
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

强调:

  • 第一:所有公共写操作(writeMessage)都是通过AsyncTask发生的,从不在主线程上发生
  • 下一步:所有写入操作都已同步并包含在BEGIN TRANSACTIONS中
  • Next:读取操作是非同步的,因为它们在写入期间不需要阻塞
  • 最后:读取操作的结果缓存在onPostExecute的主线程上

这是否代表了将大量数据写入SQLite数据库的Android最佳实践,同时最大限度地减少了对UI线程的影响?您在上面看到的伪代码是否存在明显的同步问题?

更新

我的代码中存在一个重要的错误,如下所示:

DataManager.this.mDb.execSQL("BEGIN TRANSACTION;");
Run Code Online (Sandbox Code Playgroud)

该行获取数据库的锁定.但是,它是一个DEFERRED锁,因此在写入发生之前,其他客户端都可以读写.

DataManager.this.dbWriteMessage(args[0]);
Run Code Online (Sandbox Code Playgroud)

该行实际上修改了数据库.此时,锁是一个RESERVED锁,因此没有其他客户端可以写.

请注意,在第一次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);
    }
  }
Run Code Online (Sandbox Code Playgroud)

在这种情况下,如果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;");
   }
Run Code Online (Sandbox Code Playgroud)

更新2

观看来自Google I/O 2012的视频:http: //youtu.be/gbQb1PVjfqM?t = 19m13s

它建议一种设计模式利用内置的独占事务,然后使用yieldIfContendedSafely

Nik*_*kov 2

关于同步/死锁部分,我真的不能说太多,这在很大程度上取决于代码的其余部分。由于DataManager类并不真正与 UI 交互,因此您可能希望使用服务 ( IntentService) 而不是AsyncTask. 完成同步后,您可以显示通知。onPostExecute()如果您不调用 UI 代码,则实际上不需要。