Quelles sont les meilleures pratiques pour SQLite sous Android?
Ce qui serait considéré comme les meilleures pratiques lors de l'exécution de requêtes sur une base de données SQLite dans une application Android?
Est-il sûr d'exécuter des insertions, des suppressions et des requêtes select à partir d'une AsyncTask est doInBackground? Ou dois-je utiliser le Thread d'INTERFACE utilisateur? Je suppose que les requêtes de base de données peut être "lourd" et ne doit pas utiliser le thread d'INTERFACE utilisateur comme il peut verrouiller le app - résultant en une L'Application Ne Répond Pas (ANR).
Si j'ai plusieurs AsyncTasks, devraient-ils partager une connexion ou doivent-ils ouvrir une connexion de chaque?
Sont là des pratiques exemplaires pour ces scénarios?
- Quoi que vous fassiez, n'oubliez pas de désinfecter vos données si votre fournisseur de contenu (ou SQLite interface) est orienté public!
- Vous avez certainement ne devriez PAS faire de db accède à partir de la thread de l'INTERFACE utilisateur, je peux vous dire que beaucoup.
- Pourquoi pas? Il y a sûrement des cas d'utilisation où il est valide pour ce faire?
- Si vous n'avez aucune I/O, réseau d'accès, etc. depuis le thread de l'INTERFACE utilisateur, l'ensemble de l'appareil se fige jusqu'à ce que l'opération est terminée. Si elle se termine dans 1/20 de seconde, alors tout va bien. Si cela prend plus de temps, vous avez eu une mauvaise expérience utilisateur.
Vous devez vous connecter pour publier un commentaire.
Des insertions, des mises à jour, suppressions et les lectures sont généralement OK à partir de plusieurs threads, mais Brad réponse n'est pas correct. Vous devez être prudent avec la façon dont vous créez vos connexions et de les utiliser. Il y a des situations où votre mise à jour échoue, même si votre base de données n'est pas corrompue.
La réponse basique.
La SqliteOpenHelper objet tient à une connexion de base de données. Il apparaît pour vous offrir une lecture et d'écriture de connexion, mais il n'a vraiment pas. L'appel de la lecture seule, et vous aurez l'écriture, à la connexion de base de données indépendamment.
Donc, un accompagnateur exemple, une connexion db. Même si vous l'utilisez à partir de plusieurs threads, une seule connexion à la fois. Le SqliteDatabase objet utilise java serrures pour garder l'accès sérialisé. Ainsi, si 100 threads ont un db d'instance, les appels à la réelle sur le disque de la base de données sont sérialisées.
Donc, d'une aide, d'une connexion db, ce qui est sérialisé dans du code java. Un thread, 1000 threads, si vous utilisez un accompagnateur instance partagé entre eux, l'ensemble de votre base de données code d'accès est de série. Et la vie est belle (ish).
Si vous essayez d'écrire dans la base de données à partir de données réelles distinctes connexions en même temps, on va à l'échec. Il ne va pas attendre jusqu'à la première qui est fait et puis écrire. Il sera tout simplement pas écrire votre changement. Pire, si vous n'appelez pas la bonne version de l'insertion/mise à jour sur le SQLiteDatabase, vous n'obtiendrez pas une exception. Il vous suffit de faire un message dans le LogCat, et qui le sera.
Ainsi, plusieurs threads? Utiliser un helper. Période. Si vous ne CONNAISSEZ qu'un seul thread sera écrit, vous POUVEZ être en mesure d'utiliser plusieurs connexions, et votre lit sera plus rapide, mais l'acheteur méfiez-vous. Je n'ai pas testé plus que ça.
Voici un blog avec beaucoup plus de détails et un exemple d'application.
Gris et je sont réellement enveloppant d'un outil ORM, en fonction de son Ormlite, qui fonctionne nativement avec Android la base de données de mise en œuvre, suit le coffre de la création/l'appel de la structure que je décris dans le post de blog. Qui devrait sortir très bientôt. Prendre un coup d'oeil.
Dans l'intervalle, il s'agit d'un suivi post de blog:
Également la caisse de la fourche par 2point0 de le mentionné précédemment verrouillage exemple:
getThreadSession
qui renvoie une session unique en fonction de votre fil de ce que je comprends.enableWriteAheadLogging()
il utilise plusieurs connexions (un différent pour chaque requête) pour les fils parallèles. La transaction fonctions wrapper sont synchronisés à l'interne et les autres threads attendre au lieu de lancer une exception lorsque la base de données est verrouillée.De Base De Données Simultanées Accès
Même l'article sur mon blog(j'aime le formatage plus)
J'ai écrit petit article qui décrivent la façon de rendre l'accès à l'android de base de données thread-safe.
En supposant que vous avez votre propre SQLiteOpenHelper.
Maintenant, vous voulez écrire des données dans la base de données dans des threads séparés.
Vous obtiendrez le message suivant dans votre logcat et l'un de vos modifications ne seront pas écrites.
Ce qui se passe parce que chaque fois que vous créez de nouveaux SQLiteOpenHelper objet que vous êtes en réalité de la connexion de base de données. Si vous essayez d'écrire dans la base de données à partir de données réelles distinctes connexions en même temps, on va à l'échec. (à partir de la réponse ci-dessus)
À l'utilisation de la base de données avec plusieurs threads nous devons nous assurer que nous sommes à l'aide d'une connexion de base de données.
Faisons en classe singleton Gestionnaire de Base de données qui va tenir et revenir seul SQLiteOpenHelper objet.
Mise à jour du code qui y écrire des données de la base de données dans des threads séparés ressemblera à ceci.
Cela vous amènera à un autre crash.
Puisque nous n'utilisons qu'une connexion de base de données, la méthode getDatabase() retour même instance de SQLiteDatabase objet pour Thread1 et Thread2. Ce qui se passe, Thread1 peut fermer la base de données, tandis que Thread2 est encore de l'utiliser. C'est pourquoi nous avons IllegalStateException crash.
Nous devons nous assurer que personne n'est à l'aide de la base de données et ensuite seulement de la fermer. Des gens sur stackoveflow recommandé de ne jamais fermer votre SQLiteDatabase. Ce sera résultat en suivant logcat message.
Échantillon de travail
Utiliser comme suit.
Chaque fois que vous avez besoin de la base de données, vous devez appeler openDatabase() méthode de DatabaseManager classe. À l'intérieur de cette méthode, nous avons un compteur qui indique combien de fois la base de données est ouverte. S'il est égal à un, cela signifie que nous devons créer de nouvelles connexions de base de données, si ce n'est, la connexion à la base est déjà créé.
La même chose arrive dans fermerbase() méthode. À chaque appel de cette méthode, le compteur est décrémenté, chaque fois qu'il atteint zéro, nous sommes à la fermeture de la connexion à la base.
Maintenant, vous devriez être en mesure d'utiliser votre base de données et être sûr que c'est thread-safe.
DatabaseManager.initializeInstance(getApplicationContext());
if(instance==null)
? Vous laissez pas d'autre choix que d'appeler initialiser à chaque fois; sinon, comment voulez-vous savoir si elle a été initialisée ou non dans d'autres applications, etc?initializeInstance()
a un paramètre de typeSQLiteOpenHelper
, mais dans votre commentaire que vous avez mentionné à l'utilisationDatabaseManager.initializeInstance(getApplicationContext());
. Ce qui se passe? Comment cela peut-il travailler?DatabaseManager.initializeInstance(getApplicationContext());
alors vous pouvez l'utiliser comme dans l'exemple ci-dessus.initializeInstance(SQLiteOpenHelper helper)
. Comment puis-je passer uneContext
objet où unSQLiteOpenHelper
objet est nécessaire?IllegalStateException
et il fonctionne très bien jusqu'à maintenant. stackoverflow.com/a/26728598/2105986mOpenCounter
) va être accédé par plusieurs threads, il est probablement préférable d'utiliser AtomicInteger au lieu de cela, c'est pourquoi cette classe existe.openDatabase()
de nouveau? Je suppose que c'est à faire dans mon cas.onDestroy
?Thread
ouAsyncTask
pour des opérations de longue durée (50ms+). Test de votre application pour voir où c'est. La plupart des opérations (probablement) ne nécessitent pas d'un fil, car la plupart des opérations (probablement) ne concernent que quelques lignes. Utiliser un thread pour les opérations en bloc.SQLiteDatabase
instance pour chaque DB sur le disque entre les threads et de mettre en œuvre un système de comptage de garder une trace des connexions ouvertes.Action d'un champ statique entre toutes vos classes. J'ai utilisé pour garder un singleton autour de cela et d'autres choses qui doivent être partagées. Une méthode de comptage (généralement à l'aide de AtomicInteger) devrait également être utilisées pour vous assurer de ne jamais fermer la base de données du début ou de la laisser ouverte.
Pour la version la plus récente, voir https://github.com/JakarCo/databasemanager mais je vais essayer de garder le code à jour ici. Si vous voulez comprendre ma solution, regarder le code et lire mes notes. Mes notes sont généralement assez utile.
DatabaseManager
. (ou le télécharger à partir de github)DatabaseManager
et de mettre en œuvreonCreate
etonUpgrade
comme vous le feriez normalement. Vous pouvez créer plusieurs sous-classes de la unDatabaseManager
classe afin d'avoir différentes bases de données sur le disque.getDb()
d'utiliser leSQLiteDatabase
classe.close()
pour chaque sous-classe, vous instanciéLe code de copier/coller:
close
, vous devez appeleropen
de nouveau avant d'utiliser la même instance de la classe OU vous pouvez créer une nouvelle instance. Depuis leclose
code, j'ai misdb=null
, vous ne seriez pas en mesure d'utiliser la valeur de retour degetDb
(car il serait nulle), donc, si vous voulez obtenir unNullPointerException
si vous avez fait quelque chose commemyInstance.close(); myInstance.getDb().query(...);
getDb()
etopen()
dans une seule méthode?sqLiteOpenHelper
instance de ladbMap
?La Base de données est très flexible avec le multi-threading. Mes applications de frapper leur DBs de beaucoup de différents threads simultanément et il ne fonctionne tout simplement beau. Dans certains cas, j'ai plusieurs processus de frapper la DB simultanément et qui fonctionne très bien aussi.
Votre async tâches - utiliser la même connexion lorsque vous le pouvez, mais si vous avez d', son OK pour accéder à la DB de différentes tâches.
SQLiteDatabase
des objets sur différentsAsyncTask
s/Thread
s, mais il va parfois conduire à des erreurs et c'est pourquoi SQLiteDatabase (ligne 1297) utiliseLock
s.sqLiteHelper.close
(par exemple, dansonDestroy
), tandis que certains autre thread nedb.update
oudb.insert
et etc? Ou vous n'appelez jamais.sqLiteHelper.close
?Dmytro réponse fonctionne très bien pour mon cas.
Je pense que c'est mieux de déclarer à la fonction de synchronisation. au moins pour mon cas, cela permettrait d'invoquer l'exception de pointeur null autrement, par exemple, getWritableDatabase pas encore de retour dans un thread et openDatabse appelé dans un autre thread-temps.
après avoir lutté avec ce pour un couple d'heures, j'ai constaté que vous ne pouvez utiliser qu'une db objet d'assistance par db exécution. Par exemple,
comme revêtue:
la création d'une nouvelle DBAdapter chaque itération de la boucle était la seule façon que j'ai pu obtenir mes chaînes de caractères dans une base de données par le biais de ma classe helper.
Ma compréhension de SQLiteDatabase Api, c'est que dans le cas où vous avez un multi threaded application, vous ne pouvez pas se permettre d'avoir plus d'un 1 SQLiteDatabase objet pointant vers une base de données unique.
L'objet peut certainement être créés mais les insertions/mises à jour échouent si différents threads/processus (trop) de commencer à utiliser différents SQLiteDatabase objets (comme la façon dont nous utilisons dans la Connexion JDBC).
La seule solution ici est de coller avec 1 SQLiteDatabase objets et chaque fois qu'un startTransaction() est utilisé dans plus de 1 thread, Android gère le verrouillage à travers les différents threads et permet 1 seul thread à la fois exclusifs de mise à jour des accès.
Vous pouvez aussi le faire "Lit" de la base de données et utiliser le même SQLiteDatabase objet dans un autre thread (tandis qu'un autre thread écrit) et il n'y aurait jamais de corruption de base de données, j'.e "lire fil" ne serait-ce pas lire les données de la base de données jusqu'à l'écriture "thread" engage les données, même si les deux utilisent le même SQLiteDatabase objet.
C'est différent de la façon dont la connexion de l'objet est dans JDBC où si vous passez autour de la (ou de la même) l'objet de connexion entre lire et écrire des sujets puis nous serait probablement l'impression des données non validées trop.
Dans mon enterprise application, je tente d'utiliser le conditionnel vérifie donc que le Thread d'INTERFACE utilisateur de ne jamais avoir à attendre, alors que le BG thread détient le SQLiteDatabase objet (exclusivement). J'ai essayer de prédire les Actions d'INTERFACE utilisateur et de reporter BG thread en cours d'exécution pour 'x' secondes. Aussi on peut maintenir PriorityQueue pour gérer remise SQLiteDatabase objets de Connexion de sorte que le Thread d'INTERFACE utilisateur obtient d'abord.
"read thread" wouldn't read the data from the database till the "write thread" commits the data although both use the same SQLiteDatabase object
. Ce n'est pas toujours vrai, si vous commencez à "lire le thread" juste après "écrire fil" vous ne pourriez pas obtenir la nouvelle mise à jour de données(insérée ou mise à jour à écrire thread). Lire le thread peut lire les données avant de commencer d'écrire fil. Cela se produit parce que l'opération d'écriture d'abord activer Réservés verrouillage au lieu de verrou exclusif.Vous pouvez essayer d'appliquer une nouvelle approche de l'architecture annoncés au Google I/O en 2017.
Il comprend également de nouveaux ORM bibliothèque appelée Chambre
Il contient trois composants principaux: @Entity, @Dao et @Base de données
User.java
UserDao.java
AppDatabase.java
Avoir eu quelques problèmes, je crois que j'ai compris pourquoi j'ai été mal à l'.
J'avais écrit une base de données de la classe wrapper qui comprenait une
close()
qui a appelé à l'aide d'un miroir deopen()
qui appelle getWriteableDatabase puis ont migré vers unContentProvider
. Le modèle deContentProvider
ne pas utiliserSQLiteDatabase.close()
je pense que c'est un gros indice que le code n'utilisezgetWriteableDatabase
, Dans certains cas, je faisais toujours un accès direct à l'écran de validation des requêtes dans le principal donc j'ai migré vers un getWriteableDatabase/rawQuery modèle.- Je utiliser un singleton, et il est légèrement inquiétant commentaire dans la proximité de la documentation
(mon gras).
Donc j'ai eu des plantages intermittents où je utiliser les threads d'arrière-plan pour accéder à la base de données et ils s'exécuter en même temps que le premier plan.
Donc je pense que
close()
forces de la base de données pour fermer indépendamment de tous les autres threads holding références - doncclose()
lui-même n'est pas simplement l'annulation de la correspondancegetWriteableDatabase
, mais la force de fermeture tout des demandes ouvertes. La plupart du temps ce n'est pas un problème car le code est le thread unique, mais en multi-thread cas, il y a toujours la possibilité de l'ouverture et de la fermeture de la synchronisation.Avoir lu les commentaires d'ailleurs qui explique que la SqLiteDatabaseHelper code de l'instance de compte, le temps que vous voulez un proche est l'endroit où vous voulez que la situation où vous voulez faire une copie de sauvegarde, et vous voulez que toutes les connexions être fermé et la force de SqLite à écrire loin de toute mise en cache des trucs qui pourraient être s'attarder sur - en d'autres termes arrêter toutes les applications de base de données d'activité, à proximité, juste au cas où l'aide a perdu la trace, n'importe quel fichier du niveau d'activité (sauvegarde/restauration) puis tout recommencer.
Bien que cela semble être une bonne idée d'essayer de fermer de manière contrôlée, la réalité est que Android se réserve le droit à la corbeille de votre VM, donc toute clôture est de réduire le risque de mise en cache des mises à jour qui n'est pas écrit, mais il ne peut pas être garantie si l'appareil est souligné, et si vous avez libérée correctement vos curseurs et des références à des bases de données (qui ne doit pas être statique membres) puis à l'aide d'fermeture de la base de données de toute façon.
Donc, de mon point de vue est que l'approche est la suivante:
Utilisation getWriteableDatabase pour ouvrir à partir d'un singleton wrapper. (J'ai utilisé une application dérivée de la classe afin de fournir le contexte de l'application à partir d'un statique pour résoudre le besoin d'un contexte).
Jamais directement appel à proximité.
Ne rangez jamais la résultante de la base de données dans un objet qui n'est pas évident de la portée et de s'appuyer sur le comptage de référence pour déclencher un implicite close().
Si au niveau du fichier de manutention, d'apporter toutes les activités de base de données à l'arrêt et ensuite appeler à proximité, juste au cas où il y a un emballement fil sur l'hypothèse que vous écrivez bon transactions afin de partir à la dérive thread échouera et la fermeture de la base de données va au moins avoir une bonne transactions plutôt que potentiellement un fichier de copie de niveau d'une transaction partielle.
Je sais que la réponse est tardive, mais la meilleure façon d'exécuter sqlite requêtes dans android est par l'intermédiaire d'un fournisseur de contenu personnalisé. De cette façon, l'INTERFACE utilisateur est découplé avec la base de données de la classe(la classe qui étend la SQLiteOpenHelper classe). Aussi les requêtes sont exécutées dans un thread d'arrière-plan(Chargeur du Curseur).