Cette semaine, j'ai tout appris sur ContentProvider et sur l'utilisation de la classe SQLiteOpenHelper pour gérer la création et la mise à niveau de la base de données à l'intérieur d'un fournisseur. Plus précisément, j'ai lu l'exemple du Bloc-notes dans le répertoire d'exemples du sdk.
Maintenant, je peux voir que SQLiteOpenHelper a une méthode close (). Je suis conscient que laisser des bases de données inactives ouvertes est une mauvaise pratique et peut provoquer des fuites de mémoire et ainsi de suite (à moins que la discussion this ne se dirige dans la bonne direction). Si j'utilisais la classe dans une activité, j'appellerais simplement close () dans la méthode onDestroy (), mais pour autant que je sache, ContentProvider n'a pas le même cycle de vie que les activités. Le code de NotePad ne semble jamais appeler close (), donc je voudrais supposer qu'il est géré par SQLiteOpenHelper ou une autre pièce du puzzle, mais j'aimerais vraiment être sûr. Je ne fais pas vraiment confiance à l'exemple de code, non plus ...
Résumé de la question: Quand devrions-nous fermer la base de données chez un fournisseur, le cas échéant?
Selon Dianne Hackborn (ingénieur du framework Android) il n'est pas nécessaire de fermer la base de données dans un fournisseur de contenu.
Un fournisseur de contenu est créé lors de la création de son processus d'hébergement, et reste aussi longtemps que le processus le fait, il n'est donc pas nécessaire de fermer la base de données - elle sera fermée dans le cadre du noyau nettoyant les ressources du processus lorsque le processus est tué.
Merci @bigstones de l'avoir signalé.
Cette question est un peu ancienne mais reste tout à fait pertinente. Notez que si vous faites les choses de manière "moderne" (par exemple en utilisant LoaderManager et en créant des curseurs pour charger un ContentProvider dans un thread d'arrière-plan), assurez-vous de ne PAS appeler db.close () in votre implémentation ContentProvider. J'obtenais toutes sortes de plantages liés à CursorLoader/AsyncTaskLoader quand il a essayé d'accéder au ContentProvider dans un thread d'arrière-plan, qui ont été résolus en supprimant les appels db.close ().
Donc, si vous rencontrez des accidents qui ressemblent à ceci (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
Ou ceci (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
Ou si vous voyez des messages d'erreur dans LogCat qui ressemblent à ceci:
Cursor: invalid statement in fillWindow()
Vérifiez ensuite votre implémentation ContentProvider et assurez-vous de ne pas fermer la base de données prématurément. Selon this , le ContentProvider sera nettoyé automatiquement lorsque le processus est tué de toute façon, vous n'avez donc pas besoin de fermer sa base de données à l'avance.
Cela dit, assurez-vous que vous êtes toujours correctement:
J'ai suivi Mannaz's réponse et j'ai vu que le constructeur SQLiteCursor(database, driver, table, query);
est obsolète. Ensuite, j'ai trouvé la méthode getDatabase()
et je l'ai utilisée à la place du pointeur mDatabase
; et constructeur conservé pour la capacité en arrière
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 vous souhaitez que votre base de données se ferme automatiquement, vous pouvez fournir un CursorFactory
lors de son ouverture:
mContext.openOrCreateDatabase(DB_NAME, SQLiteDatabase.OPEN_READWRITE, new LeaklessCursorFactory());
Voici les cours:
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();
}
}
}
Fermez-le lorsque vous en avez terminé, de préférence dans un bloc enfin afin que vous puissiez vous assurer que cela se produit. Je sais que cela semble un peu banal et spontané, mais c'est vraiment la seule réponse que je connaisse. Si vous ouvrez la base de données et effectuez une action, fermez-la lorsque vous avez terminé avec cette action, sauf si vous savez avec certitude qu'elle sera à nouveau nécessaire (dans ce cas, assurez-vous de la fermer une fois qu'elle n'est plus nécessaire).
Si vous utilisez votre fournisseur de contenu dans une activité, je ne pense pas que vous deviez maintenir la connexion du fournisseur de contenu. Vous pouvez simplement gérer l'objet curseur renvoyé à l'aide de startManagingCursor. Dans la méthode d'activité onPause, vous pouvez libérer le fournisseur de contenu. (vous pouvez le recharger dans onResume). En supposant que le cycle de vie de l'activité sera généralement limité, cela suffira. (Au moins selon moi;))