129 votes

Différence entre « initLoader » et « restartLoader » en « LoaderManager »

Je suis complètement perdu sur les différences entre l' initLoader et de la restartLoader fonctions de l' LoaderManager:

  • Ils ont tous deux la même signature.
  • restartLoader crée également un chargeur, si elle n'existe pas ("Démarre une nouvelle ou redémarre existant Chargeur dans ce gestionnaire").

Est-il une relation entre les deux méthodes? N'appelant restartLoader toujours appel initLoader? Puis-je appeler restartLoader sans avoir à appeler initLoader? Est-il sauver appeler initLoader deux fois pour actualiser les données? Quand dois-je utiliser l'un des deux et (important!) pourquoi?

201voto

Emanuel Moecklin Points 4779

Pour répondre à cette question, vous avez besoin de creuser dans le LoaderManager code. Bien que la documentation pour LoaderManager elle-même n'est pas assez claire (ou il n'y aurait pas cette question), la documentation de LoaderManagerImpl, une sous-classe de l'abstrait LoaderManager, est beaucoup plus instructif.

initLoader

Appel à initialiser un ID particulier avec un Chargeur. Si cet ID est déjà a un Chargeur associés avec elle, elle est restée inchangée, et de tous les précédents rappels remplacé par les nouvelles. Si il n'y a pas actuellement, un Chargeur pour l'ID, une nouvelle instance est créée et a commencé.

Cette fonction doit généralement être utilisé lorsqu'un composant est lors de l'initialisation, pour s'assurer qu'un Chargeur, il s'appuie sur est créé. Cette permet de ré-utiliser un Chargeur de données si il y en a déjà un, de sorte que, par exemple, lorsqu'une Activité est re-créée après une modification de la configuration, il n'a pas besoin de re-créer ses chargeurs.

restartLoader

Appel à re-créer le Chargeur associé à un ID spécifique. Si il y a actuellement un Chargeur associé à cet IDENTIFIANT, il sera annulé/arrêté/détruit tant que de besoin. Un nouveau Loader avec l' étant donné les arguments seront créés et ses données livrés à vous une fois de disponible.

[...] Après l'appel de cette fonction, les Chargeurs associés à ce code d'identification sera considéré comme non valide, et vous ne recevrez pas d'autres données les mises à jour à partir d'eux.

Il existe essentiellement deux cas de figure:

  1. Le chargeur avec l'id n'existe pas: les deux méthodes permettra de créer un nouveau chargeur donc il n'y a pas de différence
  2. Le chargeur avec l'id existe déjà: initLoader ne remplacer que les rappels passé comme paramètre, mais ne pas annuler ou arrêter le chargeur. Pour un CursorLoader cela signifie que le curseur reste ouverte et active (si c'était le cas avant le initLoader appel). restartLoader de l'autre coté, d'annuler, de s'arrêter et de détruire le chargeur (et fermer le sous-jacent de la source de données comme un curseur) et créer un nouveau chargeur (ce qui permettrait également de créer un nouveau curseur et de ré-exécuter 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;
}

restartLoader

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 dans le cas où le chargeur n'existe pas (info == null) les deux méthodes permettra de créer un nouveau chargeur (info = createAndInstallLoader(...)). Dans le cas où le chargeur existe déjà initLoader ne remplace les rappels (info.mCallbacks = ...) tout en restartLoader inactive de l'ancien chargeur (il sera détruit lorsque le nouveau chargeur termine son travail), puis crée un nouveau.

Ainsi dit, il est maintenant clair quand à l'utilisation initLoader et quand utiliser restartLoader et pourquoi il est logique d'avoir les deux méthodes. initLoader est utilisé pour assurer une initialisé chargeur. Si aucun n'existe, un nouveau est créé, s'il en existe déjà un, il est ré-utilisé. Nous avons toujours utiliser cette méthode, SAUF si nous avons besoin d'un nouveau chargeur, car l'exécution de la requête a changé (pas les données sous-jacentes, mais la requête réelle, comme dans l'instruction SQL d'une CursorLoader), dans ce cas, nous allons appeler restartLoader.

L'Activité/Fragment de cycle de vie n'a rien à voir avec la décision d'utiliser l'une ou l'autre méthode (et il n'y a pas besoin de garder une trace des appels à l'aide d'un one-shot drapeau comme Simon suggéré)! Cette décision est prise uniquement sur la base 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 autre requête, nous utilisons restartLoader. On peut toujours utiliser restartLoader mais que serait inefficace. Après une rotation de l'écran ou si l'utilisateur quitte l'application et retourne par la suite pour la même Activité, nous avons l'habitude de vouloir montrer le même résultat de la requête, et donc la restartLoader inutilement re-créer le chargeur et de rejeter le sous-jacent (potentiellement coûteux) résultat de la requête.

Il est très important de comprendre la différence entre les données chargées et la "requête" pour charger les données. Supposons que nous utilisons un CursorLoader de l'interrogation d'une table pour les commandes. Si une nouvelle commande est ajoutée à la table de la CursorLoader utilise onContentChanged() pour les informer de l'interface utilisateur mise à jour et de montrer la nouvelle commande (pas besoin d'utiliser restartLoader dans ce cas). Si nous voulons afficher seulement les commandes ouvertes, nous avons besoin d'une nouvelle requête, et nous voudrions utiliser restartLoader retourner une nouvelle CursorLoader reflétant la nouvelle requête.

Est-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 quand un chargeur existe déjà.

N'appelant restartLoader toujours faire appel à initLoader?

Non, il ne le fait jamais.

Puis-je appeler restartLoader sans avoir à appeler initLoader?

Oui.

Est-il sûr d'appel initLoader deux fois pour actualiser les données?

Il est possible d'appeler des initLoader deux fois, mais pas de données sera actualisée.

Quand dois-je utiliser l'un des deux et (important!) pourquoi?

Qui devrait (je l'espère) être clair, après mes explications ci-dessus.

Les modifications de Configuration

Un LoaderManager conserve son état à travers les changements de configuration (y compris les changements d'orientation de l') donc on pourrait penser il n'y a rien qui nous reste à faire. Réfléchir à nouveau...

Tout d'abord un LoaderManager ne conserve pas les rappels, donc si vous ne faites rien, vous n'aurez pas de recevoir des appels de vos méthodes de rappel comme onLoadFinished() et autres, et qui sera très probablement briser votre application. Par conséquent, nous devons faire appel au moins initLoader pour restaurer les méthodes de rappel (un restartLoader est bien sûr possible aussi). La documentation états:

Si au moment de l'appel de l'appelant est dans son état démarré, et le demandé chargeur existe déjà et a généré ses données, rappel onLoadFinished(Chargeur, 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 aurons une onLoadFinished appelez tout de suite, car les données sont déjà chargés (en supposant tel était le cas avant le changement). Alors que sonne tout droit vers l'avant, il peut être difficile (ne sommes-nous pas tous l'amour Android...).

Nous devons distinguer entre les deux cas:

  1. Gère les changements de configuration lui-même: c'est le cas pour des Fragments que l'utilisation setRetainInstance(true) ou pour une Activité avec le selon android:configChanges balise dans le manifeste. Ces les composants de ne pas recevoir un onCreate appel après par exemple un la rotation de l'écran, donc gardez à l'esprit d'appeler initLoader/restartLoader dans un autre rappel de la méthode (par exemple, dans onActivityCreated(Bundle)). Pour être en mesure d'initialiser le Chargeur(s), le chargeur id doivent être stockées (par exemple, dans une Liste). Parce que le composant est conservé à travers les modifications de la configuration, nous pouvons juste une boucle sur l'existant chargeur id et appel initLoader(loaderid, ...).
  2. Ne gère pas les changements de configuration lui-même: Dans ce cas la Les chargeurs peuvent être initialisés dans onCreate mais nous devons manuellement conserver le chargeur id ou nous ne serons pas en mesure de faire le nécessaire initLoader/restartLoader appels. Si les identifiants sont stockés dans un Liste de tableaux, nous ferions un
    outState.putIntegerArrayList(loaderIdsKey, loaderIdsArray) onSaveInstanceState et de restaurer les id dans onCreate: loaderIdsArray = savedInstanceState.getIntegerArrayList(loaderIdsKey) avant de nous rendre le initLoader appel(s).

46voto

Alex Lockwood Points 31578

Appelant initLoader lorsque le Chargeur a déjà été créé (cela arrive généralement après les modifications de configuration, par exemple) raconte l'LoaderManager de livrer le chargement de données les plus récentes à l' onLoadFinished immédiatement. Si le Chargeur n'a pas déjà été créé (lorsque l'activité ou du fragment de la première lance, par exemple) l'appel à l' initLoader raconte l'LoaderManager appeler onCreateLoader pour créer le nouveau Chargeur.

Appelant restartLoader détruit un déjà existant Chargeur (ainsi que les données associées) et raconte l'LoaderManager appeler onCreateLoader pour créer le nouveau Chargeur et de lancer une nouvelle charge.


La documentation est très clair à ce sujet:

  • initLoader assure un Chargeur est initialisé et active. Si le chargeur n'existe pas déjà, il en est créé et (si l'activité/fragment est actuellement commencé) commence le chargeur. Sinon, la dernière création du chargeur est ré-utilisé.

  • restartLoader commence une nouvelle ou redémarre existant Chargeur dans ce gestionnaire, les registres les rappels, et (si l'activité/fragment est actuellement commencé) commence à charger. Si un chargeur avec le même id a déjà été commencé, il sera automatiquement détruit lorsque le nouveau chargeur termine son travail. Le rappel sera livré avant la vieille chargeur est détruit.

16voto

Simon Points 127

Récemment, j'ai frappé un problème avec de multiples chargeur de gestionnaires et d'orientation de l'écran change et tiens à dire que, après beaucoup d'essais et d'erreurs, le modèle suivant fonctionne pour moi à la fois des Activités et des Fragments:

onCreate: call initLoader(s)
          set a one-shot flag
onResume: call restartLoader (or later, as applicable) if the one-shot is not set.
          unset the one-shot in either case.

(en d'autres termes, de définir certaines drapeau alors que initLoader est toujours exécuter une seule fois et que restartLoader est exécuté sur la 2e et la subséquente passe par onResume)

Aussi, n'oubliez pas d'attribuer des identifiants différents pour chacun de vos chargeurs dans une Activité (qui peut être un peu d'un problème avec des fragments à l'intérieur de cette activité si vous n'êtes pas prudent avec votre numérotation)


J'ai essayé d'utiliser initLoader .... n'a pas l'air de fonctionner efficacement.

Essayé initLoader sur onCreate avec null args (docs disent que c'est ok) et restartLoader (valide avec args) dans onResume ....docs sont mal & initLoader jette un nullpointer exception.

Essayé restartLoader seulement ... fonctionne pour un certain temps, mais les coups sur la 5ème ou 6ème écran de ré-orientation.

Essayé initLoader dans onResume; marche un temps et puis les coups. (plus précisément, le "Appelé doRetain lorsqu'il n'est pas commencé:"... erreur)

Essayé ce qui suit : (extrait d'un couvercle de classe qui a le chargeur id passé dans le constructeur)

/**
 * start or restart the loader (why bother with 2 separate functions ?) (now I know why)
 * 
 * @param manager
 * @param args
 * @deprecated use {@link #restart(LoaderManager, Bundle)} in onResume (as appropriate) and {@link #initialise(LoaderManager, Bundle)} in onCreate 
 */
@Deprecated 
public void start(LoaderManager manager, Bundle args) {
    if (manager.getLoader(this.id) == null) {
        manager.initLoader(this.id, args, this);
    } else {
        manager.restartLoader(this.id, args, this);
    }
}

(que j'ai trouvé quelque part dans la Pile de Débordement)

Encore une fois, cela a fonctionné pendant un certain temps mais encore jeté de l'occasionnel glitch.


De ce que je peux comprendre pendant le débogage, je pense qu'il y a quelque chose à voir avec la sauvegarde/restauration de l'état de l'instance, qui exige que initLoader(/s) sont exécutés dans le onCreate de la partie du cycle de vie, s'ils veulent survivre un tour de cycle. ( Je me trompe peut-être.)

dans le cas des Gestionnaires qui ne peuvent pas être entrepris avant que les résultats de revient d'un autre gestionnaire ou de la tâche (c'est à dire. ne peut pas être initialisé dans onCreate), je ne l'utilise initLoader. (J'ai peut-être pas correcte dans le présent, mais il semble fonctionner. Ces secondaire chargeurs ne font pas partie de l'exemple immédiat de l'état-donc, en utilisant initLoader peut effectivement être correcte dans ce cas)

lifecycle


En regardant les schémas et documents, J'aurais pensé que initLoader devrait aller dans onCreate & restartLoader dans onRestart pour les Activités, mais qui laisse des Fragments à l'aide de certains de modèle différent et je n'ai pas eu le temps de vérifier si c'est réellement stable. Peut quelqu'un d'autre commentaire sur si ils ont du succès avec ce modèle pour les activités?

0voto

user1700099 Points 74

Init chargeur lors du premier démarrage utilise loadInBackground() la méthode, sur la deuxième il sera omis. Donc, à mon avis, la meilleure solution est la suivante:

Loader<?> loa; 
try {
    loa = getLoaderManager().getLoader(0);
} catch (Exception e) {
    loa = null;
}
if (loa == null) {
    getLoaderManager().initLoader(0, null, this);
} else {
    loa.forceLoad();
}

///////////////////////////////////////////////////////////////////////////

protected SimpleCursorAdapter mAdapter;

private abstract class SimpleCursorAdapterLoader 
    extends AsyncTaskLoader <Cursor> {

    public SimpleCursorAdapterLoader(Context context) {
        super(context);
    }

    @Override
    protected void onStartLoading() {
        if (takeContentChanged() || mAdapter.isEmpty()) {
            forceLoad();
        }
    }

    @Override
    protected void onStopLoading() {
        cancelLoad();
    }

    @Override
    protected void onReset() {
        super.onReset();
        onStopLoading();
    }
}

J'ai passé beaucoup de temps à trouver cette solution -- restartLoader(...) ne fonctionnent pas correctement dans mon cas. La seule forceLoad() permet de terminer précédente de chargement sans fil de rappel (vous aurez toutes les transactions db fini correctement) et recommence à nouveau thread. Oui, il demande un peu plus de temps, mais il est plus stable. Seul le dernier démarrage du thread va prendre de rappel. Ainsi, si vous voulez faire des tests avec d'interrompre vos transactions db -- votre accueil, essayez de restartLoader(...), sinon forceLoad(). La seule commodité de restartLoader(...) est de fournir de nouvelles données initiales, je veux dire les paramètres. Et s'il vous plaît n'oubliez pas de détruire chargeur dans onDetach() la méthode de la adapté Fragment dans ce cas. Aussi garder à l'esprit que, certaines fois, lorsque vous avez une activité et, laissez-les dire, 2 fragments avec le Chargeur de chaque activité inclusive -- vous arrivez seulement 2 Chargeur de Gestionnaires, de sorte que l'Activité de ses actions LoaderManager avec Fragment (s), qui est affiché sur l'écran pendant le chargement. Essayez LoaderManager.enableDebugLogging(true); pour voir les détails de chaque cas.

-1voto

koljaTM Points 3892

Si le chargeur existe déjà, restartLoader va arrêter/annuler/détruire l'ancien, tandis que initLoader sera juste de l'initialiser avec la fonction de rappel. Je ne peux pas savoir ce que les vieux rappels de faire dans ces cas là, mais je suppose qu'ils seront abandonnés.

J'ai scanné par 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 quelle est la différence exacte est, à part que les méthodes de faire des choses différentes. Donc, je dirais, de l'utilisation initLoader la première fois et redémarrer pour que les moments suivants, bien que je ne peux pas dire avec certitude ce que chacun d'eux va faire exactement.

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