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?