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

Folks,

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

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

Все доступ к БД теперь ограничен узким классом. Вот псевдокод, показывающий, как я приближаюсь к записи в моем singleton, 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

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

Обновить

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

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

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

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

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

Заметьте, что более дорогие записи DB после первого вызова 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 действительно не взаимодействует с пользовательским интерфейсом, вы можете использовать службу ( IntentService ), а не AsyncTask . Вы можете показывать уведомления, когда вы закончите синхронизацию. Вам действительно не нужно onPostExecute() если вы не вызываете код пользовательского интерфейса.

Вы можете рассмотреть эту информацию из SDK ( http://developer.android.com/reference/android/os/AsyncTask.html)

При первом вводе AsyncTasks выполнялись последовательно на одном фоновом потоке. Начиная с DONUT, это было изменено на пул потоков, позволяющий нескольким задачам работать параллельно. Начиная с HONEYCOMB, задачи выполняются в одном потоке, чтобы избежать общих ошибок приложений, вызванных параллельным выполнением.

Если вы действительно хотите выполнить параллельное выполнение, вы можете вызвать executeOnExecutor (java.util.concurrent.Executor, Object []) с помощью THREAD_POOL_EXECUTOR.

FYI, каждый SQL-оператор, запущенный на SQLite, запускается под транзакцией, даже если вы его не укажете.

Проверьте ниже потоки, если вы делаете Bulk Insert в SQLite :

  1. Операционная система Android
  2. SQLite Bulk Insert