web-dev-qa-db-fra.com

Fermeture de la base de données dans un ContentProvider

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?

73
SilithCrowe

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

90
philipp

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:

  1. Fermeture de vos curseurs renvoyés par ContentProvider.query () . (CursorLoader/LoaderManager le fait automatiquement pour vous, mais si vous effectuez des requêtes directes en dehors du cadre LoaderManager, ou si vous avez implémenté une sous-classe CursorLoader/AsyncTaskLoader personnalisée, vous devrez vous assurer de nettoyer vos curseurs correctement.)
  2. Implémenter votre ContentProvider de manière thread-safe. (La façon la plus simple de le faire est de vous assurer que vos méthodes d'accès à la base de données sont enveloppées dans un bloc synchronisé .)
21
Steven

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);
    }
}
13
pleerock

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();
        }
    }
}
7
whlk

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

1
Ginger McMurray

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;))

0
uncaught_exceptions