Amigos,Recomendado patrón de diseño para escribir en la base de datos SQLite en Android
Busco un patrón de diseño que permite a un hilo de interfaz de usuario para interactuar con una base de datos SQLite de cliente que pueden tener las inserciones (teniendo 10s de segundos), inserciones rápidas y lecturas, y no bloquea el hilo de la interfaz de usuario.
Me gustaría obtener consejos sobre si estoy usando el patrón de diseño óptimo para esto, ya que he estado depurando recientemente problemas de interbloqueo y sincronización y no estoy 100% seguro en mi producto final.
Todos los accesos a la base de datos ahora tienen un cuello de botella a través de una clase singleton. Aquí se pseudocódigo mostrando cómo me estoy acercando a las escrituras en mi 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;
}
}
}
destacados:
- Primero: todas las operaciones de escritura pública (writeMessage) a pasar a través de un AsyncTask, nunca en el principal hilo
- siguiente: todas las operaciones de escritura se sincronizan y envueltos en iniciar las transacciones
- siguiente: las operaciones de lectura son no sincronizada, ya que no tienen que bloquean durante las escrituras
- Por último: los resultados de las operaciones de lectura se almacenan en caché en la principal hilo en el onPostExecute
¿Esto representa la mejor práctica de Android para escribir potencialmente grandes volúmenes de datos a una base de datos SQLite mientras se minimiza el impacto en el hilo de UI? ¿Hay algún problema obvio de sincronización con el pseudocódigo que ves arriba?
actualización
Hay un error significativo en mi código anterior, y es como sigue:
DataManager.this.mDb.execSQL("BEGIN TRANSACTION;");
Esa línea adquiere un bloqueo en la base de datos. Sin embargo, es un bloqueo DIFERIDO, por lo que hasta que ocurra una escritura, other clients can both read and write.
DataManager.this.dbWriteMessage(args[0]);
Esa línea realmente modifica la base de datos. En este punto, el bloqueo es un bloqueo RESERVADO, por lo que ningún otro cliente puede escribir.
Tenga en cuenta que hay más posibles grabaciones DB caros después de la primera llamada dbWriteMessage. Supongamos que cada operación de escritura ocurre en un método sincronizado protegido. Eso significa que se adquiere un bloqueo en DataManager, se produce la escritura y se libera el bloqueo. Si WriteAsyncMessageTask es el único escritor, está bien.
Supongamos ahora que hay alguna otra tarea que también escribe operaciones, pero no utiliza una transacción (porque es una escritura rápida).Esto es lo que podría ser:
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);
}
}
En este caso, si WriteSingleMessageAsyncTask está ejecutando al mismo tiempo que WriteMessageAsyncTask y WriteMessageAsyncTask ha ejecutado al menos una escritura ya, es posible que WriteSingleMessageAsyncTask para llamar dbWriteMessage, adquirir la bloquear en DataManager, pero luego ser bloqueado de completar su escritura debido al bloqueo RESERVADO. WriteMessageAsyncTask está adquiriendo y abandonando el bloqueo en DataManager repetidamente, lo cual es un problema.
Conclusión: la combinación de transacciones y el bloqueo de nivel de objeto singleton podría llevar a un interbloqueo. Asegúrese de tener el bloqueo de nivel de objeto antes de comenzar una transacción.
La solución a mi clase WriteMessageAsyncTask el original:
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;");
}
Actualización 2
Vea este video de Google I/O 2012: http://youtu.be/gbQb1PVjfqM?t=19m13s
Se sugiere un patrón de diseño haciendo uso de las transacciones exclusivas integradas y luego usando yieldIfContendedSafely
Si puedo sugerir algo en la parte de rendimiento: Una cosa que noto es que una vez que escribe nuevos mensajes, parece que está leyendo los * enteros * mensajes en el archivo db y configurando mCachedMessages. En lugar de eso, ¿podría simplemente agregar los mensajes recién agregados a la lista mCachedMessages?Esto ahorrará muchas lecturas de DB. –
@AswinKumar Eso es verdad; Quise decir que este es un ejemplo simplificado para representar las mejores prácticas en torno a la sincronización. Una optimización del rendimiento sería simplemente agregar a la lista en caché que ya está allí. – esilver