web-dev-qa-db-fra.com

Android: LoaderCallbacks.OnLoadFinished appelé deux fois

J'ai remarqué une situation étrange en utilisant Android Loaders and Fragments. Lorsque j'appelle LoaderManager.initLoader () après que le changement d'orientation onLoadFinished n'est pas appelé (bien que la documentation suggère que je devrais y être préparé)), mais il est appelé deux fois après cela. Voici un lien pour publier dans des groupes Google qui décrivent la même situation https://groups.google.com/forum/?fromgroups#!topic/Android-developers/aA2vHYxSsk . J'ai écrit un exemple application dans laquelle je n'initie que Loader simple dans Fragment.onActivityCreated () pour vérifier si cela se produit et si c'est le cas.

52
LukaszS

Vous pouvez placer la méthode initLoader () dans le rappel onResume () de votre fragment; alors onLoadFinished () du chargeur ne sera plus appelé deux fois.

    @Override
public void onResume()
{
    super.onResume();
    getLoaderManager().initLoader(0, null, this);
}
38
Bogdan Zurac

Ce problème s'est manifesté pour moi avec un CursorLoader retournant un curseur déjà fermé:

Android.database.StaleDataException: Attempted to access a cursor after it has been closed.

Je suppose que c'est un bug ou une erreur. Alors que déplacer initLoader () dans onResume peut fonctionner, ce que j'ai pu faire a été de supprimer le chargeur lorsque j'en ai fini:

Pour démarrer le chargeur (dans mon onCreate):

  getLoaderManager().initLoader(MUSIC_LOADER_ID, null, this);

Ensuite, après que j'en ai fini (essentiellement à la fin de onLoadFinished)

  getLoaderManager().destroyLoader(MUSIC_LOADER_ID);

Cela semble se comporter comme prévu, pas d'appels supplémentaires.

26
Matt

documentation initLoader dit,

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 rappel onLoadFinished (Loader, D)

Je vous suggère d'implémenter quelque chose comme la fonction onStartLoading à ce exemple

Pour un test rapide, vous pouvez essayer:

@Override protected void onStartLoading() {
    forceLoad();
}

Cela lance la fonction loadInBackground puis onLoadFinished in Fragment.

De toute façon, si vous joignez du code, je vais essayer de vous aider davantage.

6
jperera

J'ai résolu le problème d'appeler onLoadFinished deux fois comme ça. Dans votre Fragment.onActivityCreated () init votre chargeur comme ceci

if (getLoaderManager().getLoader(LOADER_ID) == null) {
    getLoaderManager().initLoader(LOADER_ID, bundle, loaderCallbacks);
} else {
    getLoaderManager().restartLoader(LOADER_ID, bundle, loaderCallbacks);

}

ici loaderCallbacks implémente vos rappels Loader habituels

private LoaderManager.LoaderCallbacks<T> loaderCallbacks
        = new LoaderManager.LoaderCallbacks<T>() {
    @Override
    public Loader<T> onCreateLoader(int id, Bundle args) {
        ...
        ...
    }

    @Override
    public void onLoadFinished(Loader<T> loader, T data) {
        ...
        ...
    }

    @Override
    public void onLoaderReset(Loader<T> loader) {
        ...
        ...
    }
};
5
Amrendra Kumar

Le problème est qu'il a appelé deux fois:
1. de Fragment.onStart
2. de FragmentActivity.onStart

La seule différence est que dans Fragment.onStart, il vérifie si mLoaderManager! = Null. Cela signifie que si vous appelez getLoadManager avant onStart, comme dans onActivityCreated, il obtiendra/créera un gestionnaire de charge et il sera appelé. Pour éviter cela, vous devez l'appeler plus tard, comme dans onResume.

3
vovkab

Lorsque vous appelez initLoader depuis onActivityCreated, vous pouvez détecter la rotation:

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    if (savedInstanceState == null) {
        // fresh new fragment, not orientation/config change
        getLoaderManager().initLoader(YOUR_LOADER_ID, null, mCallbacks);
    }
    ...
}

De cette façon, le chargeur se comporte comme prévu, résultant en un seul appel onLoadFinished.
Il n'est plus appelé lors de la rotation, donc si vous voulez les données du chargeur, vous pouvez les garder dans votre fragment, par exemple en remplaçant onSaveInstanceState.

Éditer:
Je viens de réaliser que onLoadFinished ne sera pas appelé si la rotation se produit pendant le loadInBackground du chargeur. Pour résoudre ce problème, vous devez toujours appeler initLoader après la rotation si les données du chargeur ne sont pas encore disponibles.

J'espère que cela pourra aider.

2
kiruwka

Puisque toutes les recherches sur ce sujet finissent inévitablement ici, je voulais juste ajouter mon expérience. Comme l'a dit @jperera, le coupable était que LoaderManager appellera onLoadFinished () si les chargeurs existent déjà. Dans mon cas, j'avais des fragments dans un FragmentPager et en faisant défiler 2 onglets, puis en faisant défiler à côté de lui, mon ancien fragment commencerait à se créer.

Comme placer initLoader () à l'intérieur de onCreate () provoque également des rappels doubles, j'ai placé initLoader () à l'intérieur de onResume (). Mais la séquence d'événements finit par être onCreate (), LoaderManager appelle des rappels puisque les chargeurs existent, puis onResume () est appelé, déclenchant une autre séquence initLoader () et onLoadFinished (). IE, un autre double rappel.

solution

J'ai trouvé une solution rapide par "Matt" . Une fois toutes vos données chargées (si vous avez plusieurs chargeurs), détruisez tous les chargeurs pour que leurs rappels ne soient pas appelés plus longtemps.

0
1mike12

Si vous implémentez AppCompatActivity, vérifiez que vous utilisez getSupportLoaderManager () dans les cas tous (destroyLoader/initLoader, etc.). J'avais utilisé par erreur getSupportLoaderManager () en conjonction avec un getLoaderManager () et j'ai rencontré le même problème.

0
CoastalB

j'ai fait face à ce problème. mais j'ai utilisé pour appeler la destroyloader(YOUR_ID) dans les méthodes loaderfinished. alors le chargeur n'appelle pas à nouveau la tâche d'arrière-plan deux fois.

0
prakash

Vous pouvez également comparer l'objet de données dans onLoadFinished (chargeur de chargeur, données d'objet). Si l'objet de données correspond à celui que vous avez déjà, vous ne pouvez rien faire lorsque onLoadFinished est appelé. Par exemple:

public void onLoadFinished(Loader loader, Object data) {
        if(data != null && mData != data){
            //Do something
        }
}
0
Amer Meer