web-dev-qa-db-fra.com

Comment détruire les anciens fragments dans FragmentStatePagerAdapter

Je veux implémenter ceci: enter image description here
J'utilise un ViewPager avec un FragmentStatePagerAdapter.
J'ai commencé avec l'exemple de cette page:
http://developer.Android.com/reference/Android/support/v4/app/FragmentStatePagerAdapter.html

Voici mon adaptateur ViewPager:

    public static class MyAdapter extends FragmentStatePagerAdapter {
        public MyAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public int getCount() {
            return NUM_ITEMS;
        }

        @Override
        public Fragment getItem(int position) {
            return ArrayListFragment.newInstance(position);
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            super.destroyItem(container, position, object);
        }
    }

Chaque page de mon ViewPager contient un ListView avec quelques données. Au moment où je passe à une nouvelle page dans ViewPager, cela augmentera très rapidement la mémoire RAM).
Comment retirer les anciens fragments?
J'ai aussi utilisé ceci mais ça ne fait rien:

public void destroyItem(ViewGroup container, int position, Object object) {

    FragmentManager manager = ((Fragment) object).getFragmentManager();
    FragmentTransaction trans = manager.beginTransaction();
    trans.remove((Fragment) object);
    trans.commit();

    super.destroyItem(container, position, object);
}

Il y a également un délai de 1 à 2 secondes après que je suis passé rapidement à une nouvelle page ou à une ancienne page. Existe-t-il une technique pour supprimer ce délai. Si je passe à une nouvelle page et que j'attends 2 secondes, puis au prochain commutateur, il n'y a plus de retard.

Testé sur Nexus 7.

33
vovahost

Vous ne devez pas essayer d'interférer avec la manière dont Android gère vos implémentations Fragment. La valeur par défaut pour setOffScreenPageLimit doit déjà être une. Cela signifie que Android détruira les anciens fragments lorsque la mémoire est insuffisante. Tant que vous n'avez pas de problème de mémoire, laissez-le tranquille.

La raison pour laquelle votre mémoire augmente est que Android conserve Fragment instances en mémoire pour pouvoir s'y reconnecter au lieu d'avoir à les instancier. Je vous recommande de tenir compte de la contingence de vos instances de Fragment détruites par le système d'exploitation, enregistrant leur état si cela se produit, et laissez le système d'exploitation faire son travail.

Le retard que vous rencontrez peut être dû à un calcul intensif sur le thread d'interface utilisateur. Si c'est le cas, je suggère de déplacer cela vers, par exemple, un AsyncTask. Sans le code, ce n'est cependant qu'une supposition quant à ce qui pourrait causer le problème. Mais le fait qu'il n'y ait qu'un retard initial suggère que vous chargez quelque chose qui pourrait bloquer le thread d'interface utilisateur.

Mise à jour : Jetez un œil à https://stackoverflow.com/a/9646622/170781 qui décrit très clairement comment le ViewPager gère Fragment instances.

14
Eric

J'ai eu le même problème. Mais dans mon cas, ViewPager était à l'intérieur d'un autre fragment. et après avoir supprimé ViewPagerFragment de FragmentManager, tous les fragments de FragmentStatePagerAdapter restent dans le gestionnaire de fragments. donc après quelques modifications, c'était OutOfMemoryError. Ensuite, j'active les journaux FragmentManager en:

FragmentManager.enableDebugLogging(true);

Et trouvé que l'identifiant de chaque nouveau fragment augmente à chaque fois. Cela se produit uniquement avec StatePagerAdapter. Pour résoudre ce problème, j'appelle remove pour chaque fragment qui a été instancié.

protected void dispatchOnDetach(Iterable<Fragment> fragments) {
    if (fragments == null)
        return;

    Activity aa = getActivity();
    if (aa == null)
        return;

    IBaseActivity ba = (IBaseActivity) aa;
    if (ba.isActivityStopped())
        return;

    FragmentManager frMan = ba.getSupportFragmentManager();
    FragmentTransaction frTr = frMan.beginTransaction();

    for (Fragment fr : fragments) {
        if (fr != null) {
            frTr.remove(fr);
        }
    }

    frTr.remove(this);
    frTr.commit();

}

Dans ton cas. si vous ne modifiez pas ViewPager pendant l'exécution, il est possible que le garbage collector ne puisse pas détruire vos fragments même après leur suppression du gestionnaire de fragments en raison de certaines références à ceux-ci. Vous devez vérifier si certaines classes globales les utilisent.

Et à des fins d'optimisation, vous pouvez mettre en cache chaque fragment instancié à l'aide de SoftReference ou LruCache. Exemple:

public class MyAdapter extends FragmentStatePagerAdapter {

private final LruCache<Integer, Fragment> mCache;

public MyAdapter(FragmentManager fm) {
    super(fm);
    mCache = new LruCache<Integer, Fragment>(10);
}

@Override
public int getCount() {
    return NUM_ITEMS;
}

@Override
public Fragment getItem(int position) {
    return mCache.get(position);
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    super.destroyItem(container, position, object);
}

private class MyCache extends LruCache<Integer, Fragment> {

    public MyCache(int maxSize) {
        super(maxSize);
    }

    @Override
    protected Fragment create(Integer key) {
        return ArrayListFragment.newInstance(key);
    }
}
}
14
akelix

Le FragmentStatePagerAdapter est déjà très économe en mémoire car il détruit automatiquement votre fragments inutile. Il garde simplement les vues des fragments directement à gauche et à droite de l'élément actuellement affiché et détruit les autres.

Exemple: Donc, une fois que vous glissez dans la bonne direction, il précharge le prochain fragment de voisin droit et détruit le fragment qui se trouve maintenant à deux emplacements à gauche du fragment actuellement affiché.

3
bofredo

ViewPager lui-même possède une méthode setOffscreenPageLimit qui vous permet de spécifier le nombre de pages conservées par l'adaptateur. Vos fragments éloignés seront donc détruits.

Il est un peu difficile de dire quel peut être votre problème particulier car je ne sais pas ce que fait votre fragment. Au son du délai de 1-2 secondes, il semble que vous fassiez peut-être du travail sur le thread d'interface utilisateur. De plus, que faites-vous d'autre dans votre fragment qui consomme de la mémoire? Peut-être que vous chargez des images dans un cache de mémoire statique et que vous ne les libérez pas lors de la suppression des fragments? Pourriez-vous s'il vous plaît fournir votre code de fragment, afin que je puisse voir ce qu'il fait?

En général, je recommanderais de vider un fichier HPROF de votre application au moment où il a fallu de la mémoire supplémentaire et d'analyser les références via MAT (outil d'analyseur de mémoire). Vous rencontrez clairement un problème de fuite de mémoire et je doute fortement que le problème se trouve dans les fragments eux-mêmes qui ne sont pas détruits.

Dans le cas où vous ne savez pas comment analyser le tas de mémoire, voici un bon vidéo . Je ne peux pas compter combien de fois cela m'a aidé à identifier et à éliminer les fuites de mémoire dans mes applications.

1
EvilDuck

Je pense que le problème n'est pas avec le ViewPager est sur le ListFragments. Quel type de contenu leur montrez-vous? Allouez-vous beaucoup d'images? Pouvez-vous publier le code de votre ListFragment?

J'aurais préféré faire un commentaire mais comme je n'ai pas assez de points j'espère pouvoir aider en éditant cette réponse.

1
Juangcg

Remplacez cela dans FragmentStatePagerAdapter, notez le léger changement.

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    if (position >= getCount()) {
    FragmentManager manager = ((Fragment) object).getFragmentManager();
    FragmentTransaction trans = manager.beginTransaction();
    trans.remove((Fragment) object);
    trans.commit();
}
1
Max Izrin