Fuite de mémoire Android
Comment une bonne gestion du cycle de vie peut prévenir les fuites de mémoire Android
L’exception OutOfMemoryException est un bogue commun et frustrant, et l’une des principales causes de l’arrêt inattendu d’une application.
« Pourquoi cela se produit-il maintenant, alors que l’application fonctionnait parfaitement hier ? » C’est une question qui rend perplexes les développeurs Android débutants et avancés du monde entier.
Il existe une variété de causes potentielles des exceptions OutOfMemory, mais l’une des plus courantes est la fuite de mémoire – l’allocation de mémoire dans l’app qui n’est jamais libérée. Cet article expliquera comment minimiser ce risque par une gestion efficace du cycle de vie – une partie cruciale mais souvent négligée du processus de développement.
TL;DR
Dans cet article, nous aborderons :
- Les raisons de la fuite de mémoire sur Android
- Les étapes de base pour la prévenir
- Les formes plus avancées de la gestion du cycle de vie
- Les outils qui peuvent gérer le cycle de vie de votre produit
Pourquoi la fuite de mémoire RAM se produit-elle sur Android ?
Le problème est simple. Certains objets ne devraient avoir qu’une durée de vie fixe, et lorsque leur vie utile est terminée, ils doivent être supprimés.
En théorie, cette mémoire devrait être éliminée lorsqu’un processus est tué en utilisant onStop ou onDestroy. Cependant, une mauvaise utilisation du référencement des objets peut empêcher le ramasseur d’ordures de désallouer les objets inutilisés. Par exemple : si l’objet inutilisé A référence l’objet inutilisé B, vous vous retrouverez avec deux objets inutiles que le ramasseur d’ordures ne désallouera jamais puisqu’ils se réfèrent l’un à l’autre.
Trucs courants pour empêcher que cela ne se produise
Les développeurs peuvent prendre un certain nombre de mesures pour empêcher les activités mortes d’être piégées dans la mémoire.
- Registrez/désenregistrez les BroadcastReceivers dans onResume()/onPause() ou onStart()/onStop()
- Ne JAMAIS utiliser de variables statiques pour les Views/Activities/Contexts
- Les Singletons qui ont besoin de détenir des références au Contexte devraient utiliser applicationContext() ou l’envelopper dans WeakReference
- Faites attention aux classes internes anonymes et nonstatic inner classes car elles détiennent une référence implicite à leur classe englobante.
- Utilisez des classes internes statiques au lieu d’anonymes si elles doivent survivre à la classe parente (comme les Handlers).
- Si la classe interne ou anonyme est annulable (comme AsyncTask, Thread, RxSubscriptions), annulez-la lorsque l’activité est détruite.
Les composants Android conscients du cycle de vie
Une fois que vous avez effectué les étapes de base ci-dessus, il est temps de passer à quelque chose de beaucoup plus important : le cycle de vie des activités de l’app. Si nous ne gérons pas le cycle de vie correctement, nous finirons par nous accrocher à la mémoire alors qu’elle n’est plus nécessaire.
Il y a beaucoup de tâches différentes impliquées dans ce processus. Pour chaque activité, nous devons interrompre les threads, nous débarrasser des souscriptions dans RxJava, annuler les références AsyncTask et nous assurer que la référence de cette activité (et de toute autre activité qui lui est connectée) est correctement supprimée. Toutes ces tâches peuvent drainer une quantité énorme du temps du développeur.
Les choses sont rendues encore plus compliquées par le Model View Presenter (MVP), le pattern architectural souvent employé pour construire l’interface utilisateur dans Android. Le MVP est cependant utile pour découpler la logique métier de la vue.
Dans le patron MVP, la vue et le présentateur sont tous deux des implémentations abstraites d’un contrat pour un comportement entre eux. La façon la plus commune d’implémenter le MVP est d’utiliser une Activité/Fragment comme implémentation pour la vue et une implémentation simple pour le présentateur qui a l’habitude d’avoir une référence de la vue.
On se retrouve donc avec une vue avec une référence de présentateur et un présentateur avec une référence de vue (Indice : Nous avons une fuite potentielle ici).
Compte tenu de ces difficultés potentielles, il est essentiel que nous mettions en place une structure de gestion appropriée pour supprimer l’excès de mémoire créé pendant le cycle de vie. Il existe plusieurs façons éprouvées de le faire :
Créer des composants conscients du cycle de vie en utilisant Android Arch Lifecycle sur Android Studio
Les composants conscients du cycle de vie sont intelligents. Ils peuvent réagir à un changement d’état du cycle de vie d’un autre composant, comme les activités ou les fragments, en se débarrassant de la mémoire, par exemple. Cela signifie que le code est plus léger et beaucoup plus économe en mémoire.
Arch Lifecycle est une nouvelle bibliothèque d’Android qui offre un ensemble d’outils pour construire des composants conscients du cycle de vie. La bibliothèque fonctionne de manière abstraite, ce qui signifie que les propriétaires de cycles de vie n’ont plus à se soucier de la gestion du cycle de vie de tâches et d’activités spécifiques.
Les outils clés et les définitions d’Arch Lifecycle sont les suivants :
- LifeCycle : Un système de tri qui définit quels objets ont un cycle de vie Android, et permet ensuite de les surveiller.
- LifecycleObserver : Une interface régulière qui surveille chaque objet identifié comme ayant un cycle de vie Android, en utilisant une formule simple pour traiter chaque événement clé du cycle de vie.
- @OnLifecycleEvent : Une annotation qui peut être utilisée dans les classes qui implémentent l’interface LifecycleObserver. Elle nous permet de définir des événements clés du cycle de vie qui déclencheront la méthode annotée à chaque lancement. Voici une liste de tous les événements qui peuvent être définis :
- ON_ANY
- ON_CREATE
- ON_DESTROY
- ON_PAUSE
- ON_RESUME
- ON_START
- ON_STOP
.
- LifecycleOwner est implémenté par défaut pour chaque composant Android dont le cycle de vie peut être géré, et donne au développeur le contrôle de chaque événement.
En utilisant ces outils, nous sommes en mesure d’envoyer toutes les tâches propres à leurs propriétaires (présentateurs dans notre cas), de sorte que nous avons un code propre, découplé et exempt de fuites (au moins dans la couche présentateur).
Voici une implémentation super basique pour vous montrer de quoi nous parlons :
interface View: MVPView, LifecycleOwnerclass RandomPresenter : Presenter<View>, LifecycleObserver { private lateinit var view: View override fun attachView(view: View) { this.view = view view.lifecycle.addObserver(this) } @OnLifecycleEvent(Lifecycle.Event.On_DESTROY) fun onClear() {//TODO: clean }
Utilisation des modèles de vue d’Android Arch comme présentateurs et LiveData
Une autre façon d’éviter les fuites de mémoire par une mauvaise gestion du cycle de vie est d’utiliser les nouveaux ViewModels de la bibliothèque Arch Components.
Un ViewModel est une classe abstraite qui implémente une seule fonction connue sous le nom de onClear, qui est appelée automatiquement lorsqu’un objet particulier doit être supprimé. Le ViewModel est généré par le framework et il est attaché au cycle de vie du créateur (en prime, il est super facile à injecter avec Dagger.)
En plus d’utiliser le ViewModel, LiveData fournit également un canal de communication essentiel. Le produit suit un pattern réactif et facile à observer, ce qui signifie que les objets individuels peuvent l’observer (créant un code moins couplé).
Le grand point ici est que LiveData peut être observé par un propriétaire de cycle de vie, donc le transfert de données est toujours géré par le cycle de vie -et nous pouvons nous assurer que toute référence est conservée tout en les utilisant.
Utilisation de LeakCanary et Bugfender
En plus des étapes susmentionnées, nous voulions recommander deux pièces importantes du kit : LeakCanary, un outil populaire pour surveiller les fuites, et notre propre Bugfender.
LeakCanary est une bibliothèque de détection de mémoire pour Android et Java. Elle est open-source, donc il y a une énorme communauté derrière elle, et elle ne se contente pas de vous informer sur une fuite – elle vous informe de la cause probable.
Bugfender, notre outil de journalisation à distance, vous permet de déboguer des LeakTraces individuels et d’étendre une classe appelée DisplayLeakService, qui nous permet de savoir quand une fuite est levée. Nous pouvons alors l’enregistrer facilement avec Bugfender.
public class LeakUploadService extends DisplayLeakService { override fun afterDefaultHandling(heapDump: HeapDump, result: AnalysisResult, leakInfo: String) { if (result.leakFound) { Bugfender.d("LeakCanary", result.toString()) } }}
En outre, les utilisateurs bénéficient de tous les autres avantages de Bugfender, notamment l’enregistrement des journaux 24 heures sur 24 et 7 jours sur 7 (même lorsque l’appareil est hors ligne), le rapport intégré sur les pannes et une console Web facile à utiliser, qui permet d’effectuer des forages dans les appareils individuels pour un meilleur service client.
Pour en savoir plus sur Bugfender, veuillez cliquer ici.
Leave a Reply