Люди,
Я ищу шаблон проектирования, который позволяет потоку пользовательского интерфейса взаимодействовать с базой данных 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;
}
}
}
Основные моменты:
Представляет ли это передовую практику 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