Pour répondre à cette question, vous devez fouiller dans le LoaderManager
code. Bien que la documentation de LoaderManager elle-même ne soit pas assez claire (ou il n'y aurait pas cette question), la documentation de LoaderManagerImpl, une sous-classe du résumé LoaderManager, est beaucoup plus éclairante.
initLoader
Appelez pour initialiser un ID particulier avec un chargeur. Si cet ID est déjà associé à un chargeur, il reste inchangé et tous les rappels précédents sont remplacés par les nouveaux fournis. S'il n'y a pas actuellement de chargeur pour l'ID, un nouveau est créé et démarré.
Cette fonction doit généralement être utilisée lors de l'initialisation d'un composant, pour garantir la création d'un chargeur sur lequel il s'appuie. Cela lui permet de réutiliser les données d'un chargeur existant s'il en existe déjà un, de sorte que, par exemple, lorsqu'une activité est recréée après un changement de configuration, il n'a pas besoin de recréer ses chargeurs.
redémarrer le chargeur
Appelez pour recréer le chargeur associé à un ID particulier. S'il y a actuellement un chargeur associé à cet ID, il sera annulé / arrêté / détruit selon le cas. Un nouveau chargeur avec les arguments donnés sera créé et ses données vous seront livrées une fois disponibles.
[...] Après avoir appelé cette fonction, tous les chargeurs précédents associés à cet identifiant seront considérés comme invalides et vous ne recevrez plus de mises à jour de données de leur part.
Il existe essentiellement deux cas:
- Le chargeur avec l'identifiant n'existe pas: les deux méthodes créeront un nouveau chargeur donc il n'y a pas de différence
- Le chargeur avec l'identifiant existe déjà:
initLoader
ne remplacera que les callbacks passés en paramètre mais n'annulera ni n'arrêtera le chargeur. Pour a CursorLoader
cela signifie que le curseur reste ouvert et actif (si c'était le cas avant l' initLoader
appel). `restartLoader, d'autre part, annulera, arrêtera et détruira le chargeur (et fermera la source de données sous-jacente comme un curseur) et créera un nouveau chargeur (qui créerait également un nouveau curseur et réexécutera la requête si le chargeur est un CursorLoader).
Voici le code simplifié pour les deux méthodes:
initLoader
LoaderInfo info = mLoaders.get(id);
if (info == null) {
// Loader doesn't already exist -> create new one
info = createAndInstallLoader(id, args, LoaderManager.LoaderCallbacks<Object>)callback);
} else {
// Loader exists -> only replace callbacks
info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
}
redémarrer le chargeur
LoaderInfo info = mLoaders.get(id);
if (info != null) {
LoaderInfo inactive = mInactiveLoaders.get(id);
if (inactive != null) {
// does a lot of stuff to deal with already inactive loaders
} else {
// Keep track of the previous instance of this loader so we can destroy
// it when the new one completes.
info.mLoader.abandon();
mInactiveLoaders.put(id, info);
}
}
info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
Comme nous pouvons le voir au cas où le chargeur n'existe pas (info == null) les deux méthodes créeront un nouveau chargeur (info = createAndInstallLoader (...)). Dans le cas où le chargeur existe déjà, il initLoader
ne remplace que les rappels (info.mCallbacks = ...) tout en restartLoader
désactivant l'ancien chargeur (il sera détruit lorsque le nouveau chargeur aura terminé son travail) puis en crée un nouveau.
Ainsi dit, il est maintenant clair quand utiliser initLoader
et quand utiliser restartLoader
et pourquoi il est logique d'avoir les deux méthodes.
initLoader
est utilisé pour s'assurer qu'il y a un chargeur initialisé. S'il n'en existe pas, un nouveau est créé, s'il en existe déjà un, il est réutilisé. Nous utilisons toujours cette méthode À MOINS QUE nous ayons besoin d'un nouveau chargeur car la requête à exécuter a changé (pas les données sous-jacentes mais la requête réelle comme dans l'instruction SQL pour un CursorLoader), auquel cas nous allons appeler restartLoader
.
Le cycle de vie Activité / Fragment n'a rien à voir avec la décision d'utiliser l'une ou l'autre méthode (et il n'est pas nécessaire de suivre les appels en utilisant un indicateur à un coup comme Simon l'a suggéré)! Cette décision est prise uniquement en fonction du "besoin" d'un nouveau chargeur. Si nous voulons exécuter la même requête que nous utilisons initLoader
, si nous voulons exécuter une requête différente que nous utilisons restartLoader
.
Nous pourrions toujours utiliser restartLoader
mais ce serait inefficace. Après une rotation d'écran ou si l'utilisateur quitte l'application et retourne plus tard à la même activité, nous voulons généralement afficher le même résultat de requête et donc restartLoader
recréer inutilement le chargeur et rejeter le résultat de la requête sous-jacent (potentiellement coûteux).
Il est très important de comprendre la différence entre les données chargées et la «requête» pour charger ces données. Supposons que nous utilisons un CursorLoader interrogeant une table pour les commandes. Si une nouvelle commande est ajoutée à cette table, le CursorLoader utilise onContentChanged () pour informer l'interface utilisateur de mettre à jour et d'afficher la nouvelle commande (inutile d'utiliser restartLoader
dans ce cas). Si nous voulons afficher uniquement les commandes ouvertes, nous avons besoin d'une nouvelle requête et nous l'utiliserons restartLoader
pour renvoyer un nouveau CursorLoader reflétant la nouvelle requête.
Y a-t-il une relation entre les deux méthodes?
Ils partagent le code pour créer un nouveau chargeur, mais ils font des choses différentes lorsqu'un chargeur existe déjà.
L'appel appelle-t-il restartLoader
toujours initLoader
?
Non, ça ne le fait jamais.
Puis-je appeler restartLoader
sans avoir à appeler initLoader
?
Oui.
Est-il sécuritaire d'appeler initLoader
deux fois pour actualiser les données?
Vous pouvez appeler initLoader
deux fois en toute sécurité, mais aucune donnée ne sera actualisée.
Quand devrais-je utiliser l'un des deux et pourquoi ?
Cela devrait (espérons-le) être clair après mes explications ci-dessus.
Changements de configuration
Un LoaderManager conserve son état lors des changements de configuration (y compris les changements d'orientation), vous pensez donc qu'il ne nous reste plus rien à faire. Détrompez-vous ...
Tout d'abord, un LoaderManager ne conserve pas les rappels, donc si vous ne faites rien, vous ne recevrez pas d'appels vers vos méthodes de rappel comme onLoadFinished()
et autres et cela cassera très probablement votre application.
Par conséquent, nous DEVONS appeler au moins initLoader
pour restaurer les méthodes de rappel (cela restartLoader
est bien sûr également possible). La documentation déclare:
Si au moment de l'appel l'appelant est dans son état démarré et que le chargeur demandé existe déjà et a généré ses données, alors le callback onLoadFinished(Loader, D)
sera appelé immédiatement (à l'intérieur de cette fonction) [...].
Cela signifie que si nous appelons initLoader
après un changement d'orientation, nous recevrons un onLoadFinished
appel tout de suite car les données sont déjà chargées (en supposant que c'était le cas avant le changement). Bien que cela semble simple, cela peut être délicat (nous n'aimons pas tous Android ...).
Il faut distinguer deux cas:
- Gère les changements de configuration eux-mêmes: c'est le cas pour les fragments qui utilisent setRetainInstance (true) ou pour une activité avec la
android:configChanges
balise correspondante dans le manifeste. Ces composants ne recevront pas d'appel onCreate après, par exemple, une rotation d'écran, alors gardez à l'esprit d'appeler
initLoader/restartLoader
une autre méthode de rappel (par exemple, in
onActivityCreated(Bundle)
). Pour pouvoir initialiser le (s) chargeur (s), les identifiants de chargeur doivent être stockés (par exemple dans une liste). Étant donné que le composant est conservé lors des modifications de configuration, nous pouvons simplement effectuer une boucle sur les ID de chargeur existants et appeler initLoader(loaderid,
...)
.
- Ne gère pas les changements de configuration lui-même: dans ce cas, les chargeurs peuvent être initialisés dans onCreate mais nous devons conserver manuellement les identifiants de chargeur ou nous ne pourrons pas effectuer les appels initLoader / restartLoader nécessaires. Si les identifiants sont stockés dans une ArrayList, nous ferons un
outState.putIntegerArrayList(loaderIdsKey, loaderIdsArray)
in onSaveInstanceState et restaurons les ids dans onCreate:
loaderIdsArray =
savedInstanceState.getIntegerArrayList(loaderIdsKey)
avant de faire le (s) appel (s) initLoader.
initLoader
(et que tous les rappels sont terminés, Loader est inactif) après une rotation, vous n'obtiendrez pas deonLoadFinished
rappel, mais si vous l'utilisez,restartLoader
vous le ferez?L'appel
initLoader
lorsque le chargeur a déjà été créé (cela se produit généralement après des modifications de configuration, par exemple) indique au LoaderManager de fournironLoadFinished
immédiatement les données les plus récentes du chargeur . Si le chargeur n'a pas déjà été créé (lors du premier lancement de l'activité / du fragment, par exemple), l'appel àinitLoader
indique au LoaderManager d'appeleronCreateLoader
pour créer le nouveau chargeur.L'appel
restartLoader
détruit un Loader déjà existant (ainsi que toutes les données existantes qui lui sont associées) et indique au LoaderManager d'appeleronCreateLoader
pour créer le nouveau Loader et lancer un nouveau chargement.La documentation est assez claire à ce sujet aussi:
initLoader
garantit qu'un chargeur est initialisé et actif. Si le chargeur n'existe pas déjà, un est créé et (si l'activité / le fragment est actuellement démarré) démarre le chargeur. Sinon, le dernier chargeur créé est réutilisé.restartLoader
démarre un nouveau ou redémarre un chargeur existant dans ce gestionnaire, y enregistre les rappels et (si l'activité / le fragment est actuellement démarré) commence à le charger. Si un chargeur avec le même identifiant a déjà été démarré, il sera automatiquement détruit lorsque le nouveau chargeur aura terminé son travail. Le rappel sera délivré avant la destruction de l'ancien chargeur.la source
initLoader()
inonCreate()
/onActivityCreated()
lorsque l'activité / le fragment démarre pour la première fois. De cette façon, lorsque l'utilisateur ouvre une activité pour la première fois, le chargeur sera créé pour la première fois ... mais sur tout changement de configuration ultérieur où toute l'activité / le fragment doit être détruit, l'appel suivant àinitLoader()
retournera simplement l'ancienLoader
au lieu de créer un nouveau. Habituellement, vous utilisezrestartLoader()
lorsque vous devez modifier laLoader
requête de (c'est-à-dire que vous souhaitez obtenir des données filtrées / triées, etc.).J'ai récemment rencontré un problème avec plusieurs gestionnaires de chargeurs et des changements d'orientation de l'écran et je voudrais dire qu'après de nombreux essais et erreurs, le modèle suivant fonctionne pour moi dans les activités et les fragments:
(en d'autres termes, définissez un indicateur pour que initLoader soit toujours exécuté une fois et que restartLoader soit exécuté le 2ème et les passages suivants via onResume )
N'oubliez pas non plus d'attribuer des identifiants différents pour chacun de vos chargeurs dans une activité (ce qui peut être un peu un problème avec des fragments au sein de cette activité si vous ne faites pas attention à votre numérotation)
J'ai essayé d'utiliser initLoader uniquement ... ne semblait pas fonctionner efficacement.
InitLoader essayé sur onCreate avec des arguments nuls (les documents disent que c'est ok) & restartLoader (avec des arguments valides) dans onResume .... les documents sont erronés & initLoader lève une exception de pointeur nul.
Redémarrage essayé Le chargeur uniquement ... fonctionne pendant un certain temps mais souffle sur la réorientation du 5e ou 6e écran.
J'ai essayé initLoader dans onResume ; fonctionne à nouveau pendant un certain temps et puis souffle. (en particulier l'erreur "Appelé doRetain lorsqu'il n'est pas démarré:" ...)
J'ai essayé ce qui suit: (extrait d'une classe de couverture dont l'ID du chargeur est passé au constructeur)
(que j'ai trouvé quelque part dans Stack-Overflow)
Encore une fois, cela a fonctionné pendant un certain temps, mais a toujours jeté un problème occasionnel.
D'après ce que je peux comprendre lors du débogage, je pense qu'il y a quelque chose à voir avec l'état de l'instance de sauvegarde / restauration qui nécessite que initLoader (/ s) soit exécuté dans la partie onCreate du cycle de vie s'ils doivent survivre à une rotation du cycle . ( J'ai peut-être tort.)
dans le cas des gestionnaires qui ne peuvent pas être démarrés tant que les résultats ne sont pas revenus d'un autre gestionnaire ou d'une autre tâche (c'est-à-dire ne peuvent pas être initialisés dans onCreate ), j'utilise uniquement initLoader . (Je n'ai peut-être pas raison, mais cela semble fonctionner. Ces chargeurs secondaires ne font pas partie de l'état d'instance immédiat, donc utiliser initLoader peut être correct dans ce cas)
En regardant les diagrammes et les documents, j'aurais pensé que initLoader devrait aller dans onCreate & restartLoader dans onRestart for Activities, mais cela laisse Fragments utilisant un modèle différent et je n'ai pas eu le temps de vérifier si cela est réellement stable. Quelqu'un d'autre peut-il dire s'il réussit avec ce modèle d'activités?
la source
initLoader
réutilisera les mêmes paramètres si le chargeur existe déjà. Il retourne immédiatement si les anciennes données sont déjà chargées, même si vous l'appelez avec de nouveaux paramètres. Le chargeur devrait idéalement notifier automatiquement l'activité des nouvelles données. Si l'écran tournait,initLoader
serait appelé à nouveau et les anciennes données seraient immédiatement affichées.restartLoader
est pour lorsque vous souhaitez forcer un rechargement et modifier les paramètres également. Si vous deviez créer un écran de connexion à l'aide de chargeurs, vous n'appeleriez querestartLoader
chaque fois que vous cliqueriez sur le bouton. (Le bouton peut être cliqué plusieurs fois en raison d'informations d'identification incorrectes, etc.). Vous n'appeleriez jamaisinitLoader
lors de la restauration de l'état de l'instance enregistrée de l'activité dans le cas où l'écran serait pivoté alors qu'une connexion était en cours.la source
Si le chargeur existe déjà, restartLoader arrêtera / annulera / détruira l'ancien, tandis qu'initLoader l'initialisera simplement avec le rappel donné. Je ne peux pas savoir ce que font les anciens rappels dans ces cas, mais je suppose qu'ils seront simplement abandonnés.
J'ai scanné http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/app/LoaderManager.java mais je ne trouve pas de quoi exactement la différence est, à part cela, les méthodes font des choses différentes. Donc je dirais, utilisez initLoader la première fois et redémarrez pour les fois suivantes, bien que je ne puisse pas dire avec certitude ce que chacun d'eux fera exactement.
la source
initLoader
- t - il faire dans ce cas?Initialiser le chargeur au premier démarrage utilise la méthode loadInBackground (), au deuxième démarrage, il sera omis. Donc, à mon avis, la meilleure solution est:
//////////////////////////////////////////////////// /////////////////////////
J'ai passé beaucoup de temps à trouver cette solution - restartLoader (...) ne fonctionnait pas correctement dans mon cas. Le seul forceLoad () permet de terminer le thread de chargement précédent sans rappel (ainsi vous aurez toutes les transactions db terminées correctement) et redémarre un nouveau thread. Oui, cela demande du temps supplémentaire, mais est plus stable. Seul le dernier thread démarré prendra le rappel. Ainsi, si vous voulez faire des tests avec l'interruption de vos transactions db - votre bienvenue, essayez de redémarrerLoader (...), sinon forceLoad (). La seule commodité de restartLoader (...) est de fournir de nouvelles données initiales, je veux dire des paramètres. Et n'oubliez pas de détruire loader dans la méthode onDetach () du Fragment approprié dans ce cas. Gardez également à l'esprit que parfois, lorsque vous avez une activité et, laissez-les dire, 2 fragments avec Loader pour chaque activité incluse - vous n'atteindrez que 2 gestionnaires de chargeur, donc Activity partage son LoaderManager avec le (s) fragment (s), qui s'affiche en premier à l'écran lors du chargement. Essayez LoaderManager.enableDebugLogging (true); pour voir les détails dans chaque cas.
la source
getLoader(0)
dans un fichiertry { ... } catch (Exception e) { ... }
.