2012-07-18 9 views
7

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

+0

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. –

+0

@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

Respuesta

2

No puedo decir mucho sobre la parte de sincronización/interbloqueo, que sería enormemente dependiente del resto de tu código. Dado que la clase DataManager realmente no interactúa con la interfaz de usuario, es posible que desee utilizar un servicio (IntentService) en lugar de AsyncTask. Puedes mostrar notificaciones cuando hayas terminado la sincronización. Realmente no necesita onPostExecute() si no está llamando al código UI.

+0

Suponga que la IU sí interactúa con DataManager, es decir, que el hilo de la interfaz de usuario llamará a dataManager.readMessages() cuando la actividad inicie, restaure o reciba un mensaje nuevo de que hay una actualización disponible. – esilver

+0

Lo importante es si 'DataManager' hace algo directamente relacionado con el subproceso de interfaz de usuario, es decir, actualizando texto en' TextView', etc. Si algún método de actividad accede a datos almacenados en caché global a través de 'DataManager' que no cuenta como interacción con la interfaz de usuario :) –

+1

Estoy buscando un patrón de diseño que permita que un subproceso de interfaz de usuario interactúe con una base de datos SQLite del lado del cliente que puede tener inserciones masivas, inserciones rápidas y lecturas, y no bloquea el subproceso de la interfaz de usuario. Si bien es posible tener todo el acceso a la base de datos SQLite a través de un IntentService, parece que podría ser excesivo. – esilver

1

Es posible que desee considerar esta información desde el SDK (http://developer.android.com/reference/android/os/AsyncTask.html)

Cuando se introdujo por primera vez, AsyncTasks fueron ejecutados en serie en una sola subproceso en segundo plano. Comenzando con el buñuelo, esto fue cambiado a un grupo de hilos lo que permite múltiples tareas para operar en paralelo. a partir de PANAL, las tareas se ejecutan en un solo hilo para evitar errores de aplicación comunes causadas por la ejecución en paralelo.

Si realmente desea la ejecución en paralelo, se puede en voke executeOnExecutor (java.util.concurrent.Executor, Object []) con THREAD_POOL_EXECUTOR.

0

FYI, cada instrucción SQL ejecutada en SQLite se ejecuta bajo una transacción incluso si no especifica una.

Comprobar debajo de la rosca si usted está haciendo inserción masiva en SQLite:

  1. Android Database Transaction
  2. SQLite Bulk Insert
+2

Gracias - el primer enlace es una buena introducción al uso de transacciones, y el segundo es bueno para el rendimiento. Estoy buscando un patrón de diseño que sea estrictamente correcto para el manejo de inserciones masivas, inserciones cortas y lecturas, y no bloqueará el hilo de la interfaz de usuario. – esilver

Cuestiones relacionadas