Android Fragments sur la Backstack de prendre trop de mémoire
PROBLÈME:
J'ai une application Android qui permet à un utilisateur d'accéder à un profil utilisateur ViewProfileFragment
. À l'intérieur de ViewProfileFragment
un utilisateur peut cliquer sur une image qui va l'emmener à StoryViewFragment
où les différents utilisateurs les photos montrent. Il est possible de cliquer sur une photo de profil qui les mènera vers une autre instance de ViewProfileFragment
avec le nouveau profil de l'utilisateur. Si un utilisateur clique à plusieurs reprises sur des profils d'utilisateurs, clique sur une image qui mène à la galerie, puis clique sur un autre profil, les Fragments de la pile en mémoire rapidement causant la redoutable OutOfMemoryError
. Voici un diagramme de flux de ce que je viens de décrire:
L'utilisateur clique sur Bob profil. À l'intérieur de Bob le profil de l'Utilisateur clique sur ImageA de l'emmener vers une galerie de photos de différents utilisateurs (y compris de Bob). L'utilisateur clique sur le profil de Sue, puis sur l'une de ses images - processus de répétitions, etc, etc.
UserA -> ViewProfileFragment
StoryViewFragment -> ViewProfileFragment
StoryViewFragment -> ViewProfileFragment
Donc, comme vous pouvez le voir à partir d'un flux typique, il y a beaucoup de cas de ViewProfileFragment
et StoryViewFragment
entassent dans la backstack.
CODE
Je suis le chargement de ces fragments avec la logique suivante:
//from MainActivity
fm = getSupportFragmentManager();
ft = fm.beginTransaction();
ft.replace(R.id.activity_main_content_fragment, fragment, title);
ft.addToBackStack(title);
CE QUE J'AI ESSAYÉ
1) je suis plus particulièrement à l'aide FragmentTransaction
replace
de sorte que le onPause
méthode sera déclenché lorsque l' replace
prend place. À l'intérieur de onPause
je suis en train d'essayer de libérer les ressources autant que je peux (comme l'effacement des données dans ListView
adaptateurs, "invalide" sur les variables, etc), de sorte que lorsque le fragment n'est pas active fragment et poussé sur la backstack il n'y aura plus de mémoire libérée. Mais mes efforts pour libérer des ressources n'est qu'un succès partiel. Selon MAT j'ai encore beaucoup de mémoire consommée par GalleryFragment
et ViewProfileFragment
.
2) j'ai également supprimé l'appel à addToBackStack() mais, évidemment, qui offre une expérience utilisateur médiocre parce qu'ils ne peuvent pas traverser l'arrière (l'application se ferme lorsque l'utilisateur appuie sur le bouton de retour).
3) j'ai utilisé le TAPIS de trouver tous les objets que je prends beaucoup de place et j'ai traité avec ceux de diverses manières à l'intérieur de la onPause
(et onResume
) méthodes de libérer des ressources, mais ils sont encore considérables dans la taille.
4) j'ai également écrit une boucle for dans les deux fragments onPause
qui définit l'ensemble de mes ImageViews
à null à l'aide de la logique suivante:
for (int i=shell.getHeaderViewCount(); i<shell.getCount(); i++) {
View h = shell.getChildAt(i);
ImageView v = (ImageView) h.findViewById(R.id.galleryImage);
if (v != null) {
v.setImageBitmap(null);
}
}
myListViewAdapter.clear()
QUESTIONS
1) Suis-je surplombant une façon de permettre à un Fragment de rester sur la backstack mais aussi de libérer de ses ressources, de sorte que le cycle de .remplacer(fragment) ne consomme pas la totalité de ma mémoire?
2) Quelles sont les "meilleures pratiques" lorsqu'il est prévu qu'un grand nombre de Fragments peuvent être chargées sur la backstack? Comment un développeur de traiter correctement avec ce scénario? (Ou est la logique dans mon application intrinsèquement viciée et je suis juste de faire le mal?)
Toute aide dans la réflexion d'une solution à cela serait grandement apprécié.
- Êtes-vous en utilisant le fragment de la méthode onPause?developer.android.com/reference/android/app/... je pense que vous devriez libérer les ressources bitmap associé avec le fragment sur la méthode onPause du fragment. Et pour améliorer la vitesse, spécialement si vos images sont chargées à partir de l'internet, de mettre en œuvre une image cache. Vous pouvez utiliser Image Universelle de Chargeur ou mettre en place votre propre.
- Oui le
<currentFragment>.onPause()
est atteint lorsque le.replace(new_fragment)
est émis. Je ne pense pas que c'est les images qui sont mon problème depuis que j'appellelistAdapter.clear()
dans leonPause()
méthode de suppression de toutes les données d'image. Ceux-ci sont chargés à l'aide de Pico. - Autant que je sache, le
listAdapter.clear()
ne pas libérer les ressources bitmap associé à l'ensemble du fragment... Au moins tous d'entre eux. Il ne supprime tous les éléments de la liste. - J'ai édité ma question pour montrer ce que j'ai dans mon
onPause
méthode pour aider à nettoyer les bitmaps (en appelantsetImageBitmap(null)
et de l'appel de.clear()
sur la liste de l'adaptateur. Est-il une autre/une meilleure façon de libérer les images? - Jetez un oeil à ceci. Je pense que vous êtes déjà à la suite de ces étapes, mais juste au cas où vous ne le faites pas.
- qui n'a pas de libérer les ressources utilisées par les bitmaps. Pour les libérer, vous devrez manuellement appel ((BitmapDrawable)ImageView.getDrawable()).getBitmap().le recyclage(). Mais alors vous aurez à charge les bitmaps de nouveau lors de la restauration de fragment, ou obtenir un crash.
- Je suis sûr que c'est plus un problème avec mauvais flux de navigation, il est maître-détail et cyclique dans le même temps. Même si topicstarter libère toutes les ressources possibles, cadre de toujours garder trop de mémoire alloué pour suivre backstack de l'état et, éventuellement, que va provoquer le même incident, mais avec de l'utilisateur de l'effort appliqué
- Salut @Drew.
recycle()
appel, il est seulement nécessaire sur la pré-nid d'appareils. En nid d'abeille de l'image bitmap de la mise en œuvre et de la collecte des déchets lui-même a changé, et il n'est pas nécessaire de nettoyer l'image de cette façon. Mais bitmap étaient toujours un peu délicat sur android os 😛 @MaxWorg vous êtes en train d'enregistrer les fragments dans la backstack? - Luis Encore, appelant
recycle()
est la seule façon de bataille OutOfMemoryError dans RecyclerViews/ListViews (avec juste bitmap montant) que j'ai trouvé de travail, même sur Lollipop API. Pas de.d'autres.chose.travaillé, et j'ai exeperimented avec toutes sortes de choses: l'image du chargeur de préférences, chargeur d'image des bibliothèques, quoi que ce soit. Juste comme ça. - dans mes tests (pour les applications que j'ai développées), j'ai réalisé une performance de la mémoire et du code compatible avec: -Mon propre cache Bitmap de mise en œuvre (améliore beaucoup la performance); -l'Appel à recycler, mais uniquement sur pré-nid d'appareils; -la Suppression de toutes les références inutiles lorsque l'activité/le fragment passe en premier plan, pour les rendre capables d'être nettoyé par le Garbage Collector. Il y a les principaux points que j'ai utilisé.
- citant Android site du Développeur:
On Android 2.3.3 (API level 10) and lower, using recycle() is recommended. If you're displaying large amounts of bitmap data in your app, you're likely to run into OutOfMemoryError errors. The recycle() method allows an app to reclaim memory as soon as possible.
- Laissez-nous continuer cette discussion dans le chat.
- Quelle solution avez-vous trouvé à la fin? Pouvez-vous donner un exemple de la façon dont vous êtes l'effacement des différentes ressources?
Vous devez vous connecter pour publier un commentaire.
Il s'avère que des fragments de partager le même cycle de vie que leurs parents l'activité. Selon le Fragment de la documentation:
Donc l'étape que vous avez pris pour nettoyer certaines ressources dans
onPause()
du fragment ne déclenchent pas moins que le parent de l'activité des pauses. Si vous avez plusieurs fragments qui sont en train d'être chargé par un parent de l'activité est alors probable que vous êtes en utilisant un certain type de mécanisme de commutation de celui qui est actif.Vous pourriez être en mesure de résoudre votre problème en ne s'appuyant pas sur la
onPause
mais en substituant setUserVisibleHint sur le fragment. Cela vous donne une bonne place pour déterminer où faire votre installation de ressources ou de nettoyer des ressources lorsque le fragment vient dans et hors de la vue (par exemple lorsque vous avez un PagerAdapter que les commutateurs de la FragmentA à FragmentB).Comme on l'a déjà mentionné, vous êtes empiler des éléments sur un backstack il est donc probable qu'il y aura au moins un peu de mémoire, mais vous pouvez minimiser l'empreinte par le nettoyage des ressources lorsque le fragment est hors de vue avec la technique ci-dessus.
L'autre suggestion est d'obtenir de très bons à la compréhension de la sortie de la mémoire de l'analyseur d'outil (MAT) et de la mémoire de l'analyse en général. Ici, c'est un bon point de départ. Il est vraiment facile à une fuite de mémoire dans Android, c'est une nécessité à mon avis, de se familiariser avec le concept et le fonctionnement de la mémoire peut s'éloigner de vous. Il est possible que vos problèmes sont en raison pour vous de ne pas en libérant les ressources lorsque le fragment est hors de vue ainsi que une fuite de mémoire d'une certaine sorte, donc, si vous allez l'itinéraire de l'aide
setUserVisibleHint
pour déclencher le nettoyage de vos ressources et vous voyez encore un haut volume de mémoire utilisé, une fuite de mémoire peut être le coupable, alors assurez-vous de la règle d'eux à la fois.onPause
n'est atteint lorsque le parentonPause
est appelé est ce qui a déclenché moi. Alors maintenant, mes méthodes de nettoyage sont touchés lorsque le fragment est hors de vue tout comme vous l'avez suggéré. Merci!Il est difficile de voir l'image entière (même tho vous nous avez montré beaucoup d'informations), sans concrètes d'accès à votre code source, qui je suis sûr qu'il serait difficile, si pas impossible.
Cela étant dit, il ya quelques choses à garder à l'esprit lorsque vous travaillez avec des Fragments. D'abord un morceau de avertissement.
Lorsque les Fragments ont été mis en place, ils sonnaient comme la meilleure idée de tous les temps. Être capable d'afficher plus d'une activité dans le même temps, un peu. Qui a été le point de vente.
De sorte que le monde entier lentement commencé à utiliser des Fragments. Il était le petit nouveau sur le bloc. Tout le monde était à l'aide de Fragments. Si vous n'étiez pas à l'aide de Fragments, les chances sont que "vous avez été de faire le mal".
Quelques années et applications plus tard, la tendance est (heureusement) de retour à plus d'activité, moins de fragment. Ceci est renforcé par la nouvelle Api (La capacité de faire la transition entre les activités sans que l'utilisateur vraiment s'en aperçoive, comme on le voit dans la Transition et Api par exemple).
Donc, en résumé: je déteste les fragments. Je crois que c'est l'une des pires Android implémentations de tous les temps, qui a seulement gagné en popularité en raison de l'absence de Cadre de Transition (tel qu'il existe aujourd'hui, entre les activités. Le cycle de vie d'un Fragment est, si quoi que ce soit, une boule de hasard rappels qui ne sont jamais garantis pour être appelé lorsque vous vous attendez à eux.
(Ok, j'exagère un peu, mais demandez à n'importe quel Android développeur chevronné s'il avait du mal avec les Fragments à un certain point, et la réponse est un oui retentissant).
Avec tout cela étant dit, des Fragments de travail. Et si votre solution doit fonctionner.
Donc, nous allons commencer à chercher à qui/où peut-être ces références.
note: je vais juste jeter des idées ici de la façon dont je debug, mais je ne vais pas susceptibles de fournir une solution directe. L'utiliser comme une référence.
CE QUI SE PASSE?:
Vous êtes en train d'ajouter des fragments de la Backstack.
La backstack magasins un dur de référence pour le Fragment, pas faibles ou doux. (source)
Maintenant, qui stocke une backstack? FragmentManager et... comme vous l'avez deviné, il utilise une dur de vivre de référence (de la part desource).
Et enfin, chaque activité contient une dur de référence pour la FragmentManager.
En bref: jusqu'à ce que votre activité meurt, toutes les références de ses fragments se trouvent dans la mémoire. Peu importe d'ajouter/supprimer les opérations qui s'est passé au Fragment de Gestionnaire de niveau /backstack.
QUE POUVEZ-VOUS FAIRE?
Un couple de choses viennent à mon esprit.
Essayez d'utiliser une image simple chargeur/cache lib comme Picasso, si quoi que ce soit pour vous assurer que les images ne sont pas divulgués. Vous pouvez par la suite supprimer si vous souhaitez utiliser votre propre mise en œuvre. Pour tous ses défauts, Picasso est vraiment simple à utiliser et est venu à un état où il traite de la mémoire "le droit chemin".
Après avoir enlevé le "j'ai peut-être une fuite bitmaps" problème " hors de l'image (sans mauvais jeu de mots!), alors il est temps de revoir votre Fragment du cycle de vie. Lorsque vous placez un fragment de la backstack, il n'est pas détruit, mais... vous avez une chance de les effacer de ressources:
Fragment#onDestroyView()
est appelé. Et c'est là où vous voulez être certain que le fragment annule toutes les ressources.Vous ne mentionnez pas si vos fragments en utilisant
setRetainInstance(true)
, être prudent, parce que ces dernières ne se détruit/recréé lorsque l'Activité est détruite/recréé (par exemple: rotation) et tous les points de vue peuvent être divulguées si pas correctement traitée.StoryViewFragment
.Dans l'ensemble, ce sont toutes les suggestions qui viennent de mon expérience, mais c'est vraiment dur pour vous aider à moins que nous ne voyons plus en détail.
J'espère qu'il s'avère être un point de départ.
Meilleur de la chance.
onPause
est touché (si le code est correctement) donc il faudrait beaucoup de temps avant que des problèmes de mémoire sont rencontrés. Il n'y a qu'un seul endroit dans l'ensemble de mon application en cas de problème (pas d'autres fragments d'affichage de ce comportement). Je suis à l'aide de TAPIS de sol pour affiner où les fuites pourraient venir de la. Je vais mettre à jour ma question avec plus de détails bientôt.