web-dev-qa-db-fra.com

Fuite d'un canari, Recyclerview qui fuit mAdapter

J'ai décidé qu'il était grand temps d'apprendre à utiliser Leak Canary pour détecter les fuites dans mes applications et, comme je le fais toujours, j'ai essayé de l'implémenter dans mon projet pour vraiment comprendre comment utiliser l'outil. La mise en œuvre était assez facile, la partie difficile consistait à lire ce que l’outil me renvoyait .J'ai un scrollview qui semble accumuler de la mémoire dans le gestionnaire de mémoire lorsque je fais défiler de haut en bas (même s’il ne charge pas de nouvelle Je pensais donc que c’était un bon candidat pour le suivi des fuites, voici le résultat:

 enter image description here

Il semble que v7.widget.RecyclerView présente une fuite sur l'adaptateur, et non sur mon application. Mais ça ne peut pas être vrai… d'accord?

Voici le code de l'adaptateur et de la classe qui l'utilise: https://Gist.github.com/feresr/a53c7b68145d6414c40ec70b3b842f1e

J'ai commencé une prime pour cette question car elle a refait surface après deux ans d'application complètement différente.

21
FRR

Si l'adaptateur a une durée de vie supérieure à celle de RecyclerView, vous devez effacer la référence de l'adaptateur dans onDestroyView:

@Override
public void onDestroyView() {
    recyclerView.setAdapter(null);
    super.onDestroyView();
}

Sinon, l'adaptateur contiendra une référence à la variable RecyclerView qui aurait déjà dû manquer de mémoire.

Si l'écran est impliqué dans des animations de transition, vous devez réellement aller plus loin et effacer l'adaptateur uniquement lorsque la vue est devenue détachée :

@Override
public void onDestroyView() {
    recyclerView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
        @Override
        public void onViewAttachedToWindow(View v) {
            // no-op
        }

        @Override
        public void onViewDetachedFromWindow(View v) {
            recyclerView.setAdapter(null);
        }
    });
    super.onDestroyView();
}
15
Brian Yencho

J'ai pu résoudre ce problème en remplaçant RecyclerView. Cela se produit parce que RecyclerView ne se désenregistre jamais de AdapterDataObservable.

@Override protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    if (getAdapter() != null) {
        setAdapter(null);
    }
}
9
Bolein95

Tout d’abord, je référence ce fichier .

Il semble que v7.widget.RecyclerView présente une fuite sur l'adaptateur, et non sur mon application. Mais ça ne peut pas être vrai… d'accord?

C'est en fait votre adaptateur qui laisse fuir la RecyclerView (et le graphique de trace et le titre de l'activité LeakCanary le montrent bien). Cependant, je ne sais pas s'il s'agit du "parent" RecyclerView ou de celui imbriqué dans HourlyViewHolder, ou des deux ........ Je pense que les coupables sont vos ViewHolders. En leur faisant des classes internes non statiques, vous leur fournissez explicitement la référence à la classe d'adaptateur englobante, ce qui couple presque directement l'adaptateur aux vues recyclées, car le parent de chaque itemView dans vos détenteurs est le RecyclerView lui-même.

Ma première suggestion pour résoudre ce problème serait de découpler vos ViewHolders et votre adaptateur en les rendant static des classes internes. De cette façon, ils ne contiennent pas de référence à l'adaptateur. Votre champ context leur sera donc inaccessible. C'est également une bonne chose, car les références de contexte doivent être transmises et stockées avec parcimonie (également pour éviter les fuites de mémoire importantes). . Lorsque vous avez besoin du contexte uniquement pour obtenir les chaînes, faites-le ailleurs, par exemple dans le constructeur de l'adaptateur, mais ne stockez pas le contexte en tant que membre. Enfin, la DayForecastAdapter semble aussi dangereuse: vous passez la même instance à chaque HourlyViewHolder, ce qui semble être un bogue.

Je pense que réparer le design et découpler ces classes devrait éliminer cette fuite de mémoire

5
maciekjanusz

Je ne peux pas ouvrir votre image et voir la fuite réelle, mais si vous définissez une variable locale pour votre RecyclerView dans votre Fragment et définissez la variable retainInstanceStatetrue de votre fragment, cela peut entraîner des fuites éventuelles avec rotation.

Lorsque vous utilisez une Fragment avec retainInstance, vous devez effacer toutes vos références d'interface utilisateur dans onDestroyView

@Override
public void onDestroyView() {
     yourRecyclerView = null;
     super.onDestroyView();
}

Vous trouverez ici des informations détaillées sur ce lien: Fragments conservés avec interface utilisateur et fuites de mémoire

0
savepopulation