Cerrar la base de datos en ContentProvider

Esta semana he estado aprendiendo todo sobre ContentProvider y usando la clase SQLiteOpenHelper para administrar la creación y actualización de la base de datos dentro de un proveedor. Específicamente, he estado leyendo el ejemplo de NotePad del directorio de ejemplos de SDK.

Ahora, puedo ver que SQLiteOpenHelper tiene un método close (). Soy consciente de que dejar las bases de datos inactivas abiertas es una mala práctica y puede causar pérdidas de memoria y otras cosas (a menos que esta discusión vaya en la dirección correcta). Si estuviera usando la clase en una Actividad, simplemente llamaría a close () en el método onDestroy (), pero hasta donde yo sé, ContentProvider no tiene el mismo ciclo de vida que las actividades. El código para NotePad nunca parece llamar a close (), así que me gustaría suponer que es manejado por SQLiteOpenHelper o alguna otra pieza del rompecabezas, pero realmente me gustaría saberlo con certeza. Realmente no confío en el código de muestra tanto, tampoco …

Resumen de la pregunta: ¿Cuándo deberíamos cerrar la base de datos en un proveedor, si es que lo hacemos?

Según Dianne Hackborn (ingeniero de framework de Android) no hay necesidad de cerrar la base de datos en un proveedor de contenido.

Se crea un proveedor de contenido cuando se crea su proceso de alojamiento, y se mantiene durante todo el tiempo que dure el proceso, por lo que no es necesario cerrar la base de datos; se cerrará como parte del kernel limpiando los recursos del proceso cuando el proceso es asesinado.

Gracias @bigstones por señalar esto.

Esta pregunta es un poco antigua, pero sigue siendo bastante relevante. Tenga en cuenta que si está haciendo las cosas de la manera “moderna” (por ejemplo, usando LoaderManager y creando CursorLoaders para consultar un ContentProvider en un hilo de fondo), asegúrese de NO llamar a db.close () en su implementación de ContentProvider. Obtuve todo tipo de lockings relacionados con CursorLoader / AsyncTaskLoader cuando intentaba acceder al ContentProvider en un hilo de fondo, que se resolvió al eliminar las llamadas db.close ().

Entonces, si te encuentras con choques que se ven así (Jelly Bean 4.1.1):

Caused by: java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed. at android.database.sqlite.SQLiteConnectionPool.throwIfClosedLocked(SQLiteConnectionPool.java:962) at android.database.sqlite.SQLiteConnectionPool.waitForConnection(SQLiteConnectionPool.java:677) at android.database.sqlite.SQLiteConnectionPool.acquireConnection(SQLiteConnectionPool.java:348) at android.database.sqlite.SQLiteSession.acquireConnection(SQLiteSession.java:894) at android.database.sqlite.SQLiteSession.executeForCursorWindow(SQLiteSession.java:834) at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:62) at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:143) at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:133) at android.content.ContentResolver.query(ContentResolver.java:388) at android.content.ContentResolver.query(ContentResolver.java:313) at com.hindsightlabs.paprika.loaders.GroceryListLoader.loadInBackground(GroceryListLoader.java:147) at com.hindsightlabs.paprika.loaders.GroceryListLoader.loadInBackground(GroceryListLoader.java:1) at android.support.v4.content.AsyncTaskLoader.onLoadInBackground(AsyncTaskLoader.java:240) at android.support.v4.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:51) at android.support.v4.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:40) at android.support.v4.content.ModernAsyncTask$2.call(ModernAsyncTask.java:123) at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305) ... 4 more 

O esto (ICS 4.0.4):

 Caused by: java.lang.IllegalStateException: database /data/data/com.hindsightlabs.paprika/databases/Paprika.db (conn# 0) already closed at android.database.sqlite.SQLiteDatabase.verifyDbIsOpen(SQLiteDatabase.java:2215) at android.database.sqlite.SQLiteDatabase.lock(SQLiteDatabase.java:436) at android.database.sqlite.SQLiteDatabase.lock(SQLiteDatabase.java:422) at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:79) at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:164) at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:156) at android.content.ContentResolver.query(ContentResolver.java:318) at android.support.v4.content.CursorLoader.loadInBackground(CursorLoader.java:49) at android.support.v4.content.CursorLoader.loadInBackground(CursorLoader.java:35) at android.support.v4.content.AsyncTaskLoader.onLoadInBackground(AsyncTaskLoader.java:240) at android.support.v4.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:51) at android.support.v4.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:40) at android.support.v4.content.ModernAsyncTask$2.call(ModernAsyncTask.java:123) at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305) ... 4 more 

O si está viendo mensajes de error en LogCat que se ven así:

 Cursor: invalid statement in fillWindow() 

Luego verifica la implementación de ContentProvider y asegúrate de no estar cerrando la base de datos prematuramente. De acuerdo con esto , ContentProvider se limpiará automáticamente cuando el proceso se mate de todos modos, por lo que no es necesario cerrar su base de datos con anticipación.

Dicho esto, asegúrate de que sigas siendo correctamente:

  1. Cierre sus cursores devueltos por ContentProvider.query () . (CursorLoader / LoaderManager hace esto automáticamente, pero si está realizando consultas directas fuera del marco de LoaderManager, o si ha implementado una subclase personalizada CursorLoader / AsyncTaskLoader, deberá asegurarse de que está limpiando sus cursores correctamente.)
  2. Implementando su ContentProvider de una manera segura. (La forma más sencilla de hacerlo es asegurarse de que los métodos de acceso a la base de datos estén incluidos en un bloque sincronizado ).

He seguido la respuesta de Mannaz y vi que SQLiteCursor(database, driver, table, query); constructor está en desuso. Luego encontré el método getDatabase() y lo usé en lugar del puntero mDatabase ; y mantiene el constructor para la capacidad de retroceder

 public class MyOpenHelper extends SQLiteOpenHelper { public static final String TAG = "MyOpenHelper"; public static final String DB_NAME = "myopenhelper.db"; public static final int DB_VESRION = 1; public MyOpenHelper(Context context) { super(context, DB_NAME, new LeaklessCursorFactory(), DB_VESRION); } //... } public class LeaklessCursor extends SQLiteCursor { static final String TAG = "LeaklessCursor"; public LeaklessCursor(SQLiteDatabase db, SQLiteCursorDriver driver, String editTable, SQLiteQuery query) { super(db, driver, editTable, query); } @Override public void close() { final SQLiteDatabase db = getDatabase(); super.close(); if (db != null) { Log.d(TAG, "Closing LeaklessCursor: " + db.getPath()); db.close(); } } } public class LeaklessCursorFactory implements CursorFactory { @Override public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery, String editTable, SQLiteQuery query) { return new LeaklessCursor(db,masterQuery,editTable,query); } } 

Si desea que su base de datos se cierre automáticamente, puede proporcionar un CursorFactory al abrirlo:

 mContext.openOrCreateDatabase(DB_NAME, SQLiteDatabase.OPEN_READWRITE, new LeaklessCursorFactory()); 

Estas son las clases:

 public class LeaklessCursorFactory implements CursorFactory { @Override public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery, String editTable, SQLiteQuery query) { return new LeaklessCursor(db,masterQuery,editTable,query); } } public class LeaklessCursor extends SQLiteCursor { static final String TAG = "LeaklessCursor"; final SQLiteDatabase mDatabase; public LeaklessCursor(SQLiteDatabase database, SQLiteCursorDriver driver, String table, SQLiteQuery query) { super(database, driver, table, query); mDatabase = database; } @Override public void close() { Log.d(TAG, "Closing LeaklessCursor: " + mDatabase.getPath()); super.close(); if (mDatabase != null) { mDatabase.close(); } } } 

Ciérrela cuando haya terminado con ella, preferiblemente en un bloque final para que pueda asegurarse de que suceda. Sé que suena un poco trillado y fuera de lo común, pero en realidad es la única respuesta que conozco. Si abre la base de datos y realiza una acción, ciérrela cuando haya terminado con esa acción a menos que sepa con certeza que será necesaria nuevamente (en tal caso, asegúrese de cerrarla cuando ya no la necesite).

Si está utilizando su proveedor de contenido dentro de una actividad, entonces no creo que tenga que mantener la conexión del proveedor de contenido. Podrías simplemente administrar el objeto de cursor devuelto usando startManagingCursor. En el método de actividad onPause, puede liberar el proveedor de contenido. (puede volver a cargarlo en Reanudar). Suponiendo que el ciclo de vida de la actividad generalmente será limitado, esto sería suficiente. (Por lo menos según yo;))