73 votes

Fermeture de la base de données dans un ContentProvider

Cette semaine, j'ai tout appris sur ContentProvider et l'utilisation de la classe SQLiteOpenHelper pour gérer la création et la mise à jour de la base de données à l'intérieur d'un fournisseur. Plus précisément, j'ai lu l'exemple NotePad du répertoire samples 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 causer des fuites de mémoire et autres (à moins que les bases de données de SQLiteOpenHelper ne soient fermées). ce la discussion va 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 du Bloc-notes ne semble jamais appeler close(), donc je suppose que c'est géré par SQLiteOpenHelper ou une autre pièce du puzzle, mais j'aimerais vraiment en être sûr. Je n'ai pas vraiment confiance dans l'exemple de code, non plus...

Résumé de la question : Quand doit-on fermer la base de données d'un fournisseur, si tant est qu'on le fasse ?

93voto

philipp Points 1327

Selon Dianne Hackborn (ingénieur du cadre 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éé lorsque son processus d'hébergement est créé, et reste en place aussi longtemps que le processus, il n'est donc pas nécessaire de de fermer la base de données -- elle sera fermée lors du nettoyage du noyau. nettoie les ressources du processus lorsque ce dernier est tué.

Merci @bigstones de l'avoir signalé.

21voto

Steven Wei Points 664

Cette question est un peu ancienne mais elle est toujours d'actualité. Notez que si vous faites les choses de la manière "moderne" (par exemple en utilisant LoaderManager et en créant des CursorLoaders pour interroger un ContentProvider en arrière-plan), assurez-vous que vous n'appelez PAS db.close() dans votre implémentation du ContentProvider. J'obtenais toutes sortes de plantages liés à CursorLoader/AsyncTaskLoader quand il essayait 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 plantages qui ressemblent à ça (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 l'implémentation de votre ContentProvider et assurez-vous que vous ne fermez pas la base de données prématurément. D'après ce En effet, le ContentProvider sera de toute façon nettoyé automatiquement lorsque le processus sera tué. Il n'est donc pas nécessaire de fermer sa base de données à l'avance.

Cela dit, assurez-vous que vous êtes toujours correctement :

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

13voto

Уmed Points 3410

J'ai suivi Mannaz's réponse et a vu que SQLiteCursor(database, driver, table, query); est déprécié. Ensuite, j'ai trouvé getDatabase() et l'utiliser à la place de la méthode mDatabase un pointeur ; et un constructeur conservé pour la capacité de retour 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);
    }
}

7voto

Mannaz Points 6351

Si vous voulez que votre base de données se ferme automatiquement, vous pouvez fournir un numéro d'identification de la base de données. CursorFactory en l'ouvrant :

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

Voici les classes :

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

1voto

James McMurray Points 659

Fermez-le lorsque vous en avez terminé, de préférence dans un bloc final afin de vous assurer que cela se produit. Je sais que cela peut paraître un peu banal, 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é cette action, à moins que vous ne soyez certain qu'elle sera à nouveau nécessaire (dans ce cas, assurez-vous de la fermer lorsqu'elle n'est plus nécessaire).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X