Base de données Android SQLite Quand fermer

96

Je travaille avec une base de données SQLite sur Android. Mon gestionnaire de base de données est un singleton et ouvre maintenant une connexion à la base de données lorsqu'elle est initialisée. Il est prudent de laisser la base de données ouverte tout le temps pour que lorsque quelqu'un appelle ma classe pour travailler avec la base de données, elle est déjà ouverte? Ou devrais-je ouvrir et fermer la base de données avant et après chaque accès est nécessaire. Y a-t-il un mal à le laisser ouvert tout le temps?

Merci!

w.donahue
la source

Réponses:

60

Je le garderais ouvert tout le temps et le fermerais dans une méthode de cycle de vie telle que onStopou onDestroy. de cette façon, vous pouvez facilement vérifier si la base de données est déjà utilisée en appelant isDbLockedByCurrentThreadou isDbLockedByOtherThreadssur l' SQLiteDatabaseobjet unique à chaque fois avant de l'utiliser. cela empêchera de multiples manipulations de la base de données et sauvera votre application d'un crash potentiel

donc dans votre singleton, vous pourriez avoir une méthode comme celle-ci pour obtenir votre seul SQLiteOpenHelperobjet:

private SQLiteDatabase db;
private MyDBOpenHelper mySingletonHelperField;
public MyDBOpenHelper getDbHelper() {
    db = mySingletonHelperField.getDatabase();//returns the already created database object in my MyDBOpenHelper class(which extends `SQLiteOpenHelper`)
    while(db.isDbLockedByCurrentThread() || db.isDbLockedByOtherThreads()) {
        //db is locked, keep looping
    }
    return mySingletonHelperField;
}

donc chaque fois que vous souhaitez utiliser votre objet d'assistance ouvert, appelez cette méthode getter (assurez-vous qu'elle est filetée)

une autre méthode de votre singleton peut être (appelée CHAQUE FOIS avant d'essayer d'appeler le getter ci-dessus):

public void setDbHelper(MyDBOpenHelper mySingletonHelperField) {
    if(null == this.mySingletonHelperField) {
        this.mySingletonHelperField = mySingletonHelperField;
        this.mySingletonHelperField.setDb(this.mySingletonHelperField.getWritableDatabase());//creates and sets the database object in the MyDBOpenHelper class
    }
}

vous pouvez également fermer la base de données dans le singleton:

public void finalize() throws Throwable {
    if(null != mySingletonHelperField)
        mySingletonHelperField.close();
    if(null != db)
        db.close();
    super.finalize();
}

si les utilisateurs de votre application ont la possibilité de créer très rapidement de nombreuses interactions de base de données, vous devez utiliser quelque chose comme je l'ai démontré ci-dessus. mais s'il y a des interactions minimales avec la base de données, je ne m'inquiéterais pas à ce sujet, et créer et fermer la base de données à chaque fois.

James
la source
Je pourrais accélérer l'accès à la base de données d'un facteur 2 avec cette approche (garder la base de données ouverte), merci
teh.fonsi
2
Le filage est une très mauvaise technique. Voir ma réponse ci-dessous.
mixel
1
@mixel bon point. Je crois avoir publié cette réponse avant que l'API 16 ne soit disponible, mais je peux me tromper
James
1
@binnyb Je pense qu'il vaut mieux mettre à jour votre réponse pour ne pas induire les gens en erreur.
mixel
3
WARN isDbLockedByOtherThreads () est Depricated et renvoie false depuis l'API 16. Vérifier également isDbLockedByCurrentThread () donnera une boucle infinie car il arrête le thread en cours et rien ne peut «déverrouiller DB» de sorte que cette méthode retourne true.
matreshkin
20

À partir de maintenant, il n'est pas nécessaire de vérifier si la base de données est verrouillée par un autre thread. Pendant que vous utilisez le singleton SQLiteOpenHelper dans chaque thread, vous êtes en sécurité. De la isDbLockedByCurrentThreaddocumentation:

Le nom de cette méthode vient d'une époque où le fait d'avoir une connexion active à la base de données signifiait que le thread détenait un verrou réel sur la base de données. De nos jours, il n'y a plus de véritable «verrou de base de données» bien que les threads puissent se bloquer s'ils ne peuvent pas acquérir une connexion à la base de données pour effectuer une opération particulière.

isDbLockedByOtherThreads est obsolète depuis le niveau d'API 16.

mixel
la source
Il ne sert à rien d'utiliser une instance par thread. SQLiteOpenHelper est thread-safe. C'est aussi une mémoire très inefficace. Au lieu de cela, une application doit conserver une seule instance de SQLiteOpenHelper par base de données. Pour une meilleure concurrence, il est recommandé d'utiliser WriteAheadLogging qui fournit un pool de connexions pour jusqu'à 4 connexions.
ejboy
@ejboy c'est ce que je voulais dire. "Utilisez singleton (= un) SQLiteOpenHelper dans chaque thread", pas "par thread".
mixel
15

Concernant les questions:

Mon gestionnaire de base de données est un singleton et ouvre maintenant une connexion à la base de données lorsqu'elle est initialisée.

Nous devrions diviser «ouverture DB», «ouverture d'une connexion». SQLiteOpenHelper.getWritableDatabase () donne une base de données ouverte. Mais nous n'avons pas à contrôler les connexions car cela se fait en interne.

Il est prudent de laisser la base de données ouverte tout le temps pour que lorsque quelqu'un appelle ma classe pour travailler avec la base de données, elle est déjà ouverte?

Oui, ça l'est. Les connexions ne se bloquent pas si les transactions sont correctement fermées. Notez que votre BD sera également fermée automatiquement si GC la finalise.

Ou devrais-je ouvrir et fermer la base de données avant et après chaque accès est nécessaire.

La fermeture de l'instance SQLiteDatabase ne donne rien de formidable à part la fermeture des connexions, mais c'est mauvais pour un développeur s'il y a des connexions à ce moment. De plus, après SQLiteDatabase.close (), SQLiteOpenHelper.getWritableDatabase () renverra une nouvelle instance.

Y a-t-il un mal à le laisser ouvert tout le temps?

Non, il n'y en a pas. Notez également que la fermeture de la base de données à un moment et un thread sans rapport, par exemple dans Activity.onStop (), peut fermer les connexions actives et laisser les données dans un état incohérent.

matreshkin
la source
Merci, après avoir lu vos mots "après SQLiteDatabase.close (), SQLiteOpenHelper.getWritableDatabase () renverra une nouvelle instance" Je me suis rendu compte que j'ai enfin une réponse à un ancien problème de mon application. À un problème, devenu critique après la migration vers Android 9 (voir stackoverflow.com/a/54224922/297710 )
yvolk
1

Du point de vue des performances, la manière optimale est de conserver une seule instance de SQLiteOpenHelper au niveau de l'application. L'ouverture de la base de données peut être coûteuse et constitue une opération de blocage, elle ne doit donc pas être effectuée sur le thread principal et / ou dans les méthodes du cycle de vie de l'activité.

La méthode setIdleConnectionTimeout () (introduite dans Android 8.1) peut être utilisée pour libérer de la RAM lorsque la base de données n'est pas utilisée. Si le délai d'inactivité est défini, la ou les connexions à la base de données seront fermées après une période d'inactivité, c'est-à-dire lorsque la base de données n'a pas été accédée. Les connexions seront rouvertes de manière transparente avec l'application, lorsqu'une nouvelle requête est exécutée.

En plus de cela, une application peut appeler releaseMemory () lorsqu'elle passe en arrière-plan ou détecte une pression mémoire, par exemple dans onTrimMemory ()

ejboy
la source
0

Vous pouvez également utiliser ContentProvider. Il fera cela pour vous.

Grégory Buiko
la source
-9

Créez votre propre contexte d'application, puis ouvrez et fermez la base de données à partir de là. Cet objet possède également une méthode OnTerminate () que vous pouvez utiliser pour fermer la connexion. Je ne l'ai pas encore essayé, mais cela semble être une meilleure approche.

@binnyb: Je n'aime pas utiliser finalize () pour fermer la connexion. Cela pourrait fonctionner, mais d'après ce que j'ai compris, écrire du code dans une méthode Java finalize () est une mauvaise idée.

Maigre
la source
Vous ne voulez pas vous fier à finalize car vous ne savez jamais quand il sera appelé. Un objet peut rester dans les limbes jusqu'à ce que le GC décide de le nettoyer, et alors seulement finalize () sera appelé. C'est pourquoi c'est inutile. La bonne façon de le faire est d'avoir une méthode qui est appelée lorsque l'objet n'est plus utilisé (qu'il soit ramassé ou non), et c'est exactement ce qu'est onTerminate ().
erwan
5
Les documents disent assez clairement que onTerminatecela ne sera pas appelé dans un environnement de production - developer.android.com/reference/android/app/...
Eliezer