Рекомендуемый шаблон проектирования для записи в базу данных SQLite в Android

Люди,

Я ищу шаблон проектирования, который позволяет потоку пользовательского интерфейса взаимодействовать с базой данных SQLite на стороне клиента -, которая может иметь массовые вставки (, занимающие 10 секунд ), быстрые вставки и чтение, а не заблокировать поток пользовательского интерфейса.

Я хотел бы получить совет относительно того, использую ли я для этого оптимальный шаблон проектирования, так как недавно я занимался отладкой взаимоблокировок и проблем с синхронизацией, и я не уверен на 100% в своем конечном продукте.

Весь доступ к БД теперь ограничен одноэлементным классом. Вот псевдокод, показывающий, как я подхожу к записи в моем синглтоне DataManager:

public class DataManager {

  private SQLiteDatabase mDb;
  private ArrayList mCachedMessages;

  public ArrayList 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 dbReadMessages() {
    // SQLite query for messages
  }

  private class WriteMessageAsyncTask extends AsyncTask> {
    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 newMessages = DataManager.this.dbReadMessages();
       return newMessages;
    }
    protected void onPostExecute(ArrayList newMessages) {
       DataManager.this.mCachedMessages = newMessages;
    }
  }
}

Основные моменты:

  • Сначала :все публичные операции записи (writeMessage )происходят через AsyncTask, а не на основной резьба
  • Далее :все операции записи синхронизируются и заключаются в НАЧАТЬ ТРАНЗАКЦИИ
  • Следующие :операции чтения не синхронизированные -, так как они не должны блокироваться во время записи
  • Наконец :результаты операций чтения кэшируются на главном поток в onPostExecute

Представляет ли это передовую практику Android для записи потенциально больших объемов данных в базу данных SQLite при минимизации воздействия на поток пользовательского интерфейса? Есть ли очевидные проблемы синхронизации с псевдокодом, который вы видите выше?

Обновление

В приведенном выше коде есть существенная ошибка, и она выглядит следующим образом:

DataManager.this.mDb.execSQL("BEGIN TRANSACTION;");

Эта строка устанавливает блокировку базы данных. Однако это блокировка DEFERRED, поэтому, пока не произойдет запись, другие клиенты могут как читать, так и записывать .

DataManager.this.dbWriteMessage(args[0]);

Эта строка фактически изменяет базу данных. В этот момент блокировка является ЗАРЕЗЕРВИРОВАННОЙ блокировкой, поэтому другие клиенты не могут писать.

Обратите внимание, что после первого вызова dbWriteMessage возможно более дорогостоящие операции записи в БД. Предположим, что каждая операция записи выполняется в защищенном синхронизированном методе. Это означает, что на DataManager устанавливается блокировка, происходит запись и блокировка снимается. Если WriteAsyncMessageTask является единственным модулем записи, это нормально.

Теперь давайте предположим, что есть какая-то другая задача, которая также выполняет операции записи, но не использует транзакцию (, потому что это быстрая запись ). Вот как это может выглядеть:

  private class WriteSingleMessageAsyncTask extends AsyncTask {
    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

7
задан esilver 18 July 2012 в 21:48
поделиться