Trucs et astuces pour optimiser la consommation de mémoire des applications Android

0
1085
Introduction

L’allocation et la libération de mémoire sur Android est souvent une opération non dénuée de conséquences. Le proverbe chinois qui dit : « Il est facile d’être dans le luxe quand on est frugal, difficile d’être frugal quand on est dans le luxe » décrit parfaitement ce qu’on appelle l’utilisation de la mémoire.

Imaginons le pire scénario qui consiste à compiler une application contenant des millions de lignes de code, quand soudain, un crash Out Of Memory (OOM) apparaît. Vous commencez le débogage de l’application et l’analyse du fichier hprof. Heureusement que vous avez détecté l’origine de la cause et que vous avez corrigé votre bouffeur de mémoire. Mais il arrive des fois que vous trouvez plusieurs petites variables temporaires ou membres qui allouent de la mémoire, rendant impossible de simples corrections. Cela signifie que vous devez factoriser le code, ce qui peut engendrer des risques supplémentaires, tout cela pour préserver quelques kilobytes, voir quelques bytes de mémoire.

Cet article introduit la gestion de mémoire Android (Android Memory Management) et explique différents aspects qui jouent un rôle dans le système de gestion. De plus, l’amélioration de la gestion de mémoire, la détection et le contournement des fuites de mémoire et l’analyse de l’utilisation de mémoire sont également couverts par cet article.

Gestion de mémoire Android

Android utilise paging et mmap au lieu de fournir un espace swap, ce qui signifie que toute mémoire affectée par votre application ne pourrait être paginée que si vous libérer toute référence.

La taille de Dalvik Virtual Machine pour les processus d’application est limitée. Les applications démarrent avec 2mb, et l’allocation maximale, « largeHeap », est limitée à 36mb (Selon la configuration spécifique du Device). Comme exemple d’applications « largeHeap », nous citons les applications d’édition de photo/vidéo, caméra, galerie, page d’accueil.

Android stocke les processus des applications en arrière-plan dans un cache LRU. Quand la mémoire d’un système devient insuffisante, Android détruit les processus selon la stratégie LRU, mais considérera également laquelle des applications qui consomme plus de mémoire. Actuellement, le nombre maximal des processus en arrière-plan est de 20 (Selon la configuration spécifique du device). Si vous voulez que votre application reste plus longtemps en mode arrière-plan, vous devez libérer de la mémoire inutile avant de mettre votre application en arrière-plan ainsi le système Android sera moins susceptibles de générer un message d’erreur ou même de fermer l’application.

Comment améliorer l’utilisation de mémoire

Android est une plateforme mobile mondiale, des millions de développeurs Android se désignent la tâche de concevoir des applications stables et scalables. Voici une liste d’astuces et de bonnes pratiques pour améliorer l’utilisation de la mémoire des applications Android :

  1. Utilisez le Design Pattern contenant « Abstraction » avec prudence. Bien que, de point de vue Design Pattern, l’abstraction aide à concevoir une architecture logicielle plus solide, elle peut, sous mobile, avoir des effets latéraux indésirables à cause du code supplémentaire à exécuter. Ceci engendre un coût supplémentaire, en temps et en mémoire. Malgré les bénéfices signifiants apportés par l’abstraction, il est fortement recommandé de ne pas l’utiliser.
  2. Évitez l’utilisation du type « enum ». Ce type doublera l’utilisation mémoire par rapport à une constante statique normale, alors ne l’utilisez pas.
  3. Essayer d’utiliser les containers optimisés « SparseArray, SparseBooleanArray et LongSparseArray » au lieu de « HashMap ». HashMap alloue un entry object à chaque mapping, ce qui constitue une action ineffective en terme de mémoire. De plus, ceci engendrera une multiplication du comportement « autoboxing/unboxing », réputé très peu performant. Par contre, les containers SparseArray et similaires mappent les clés dans un plain array. Rappelez-vous que ces containers optimisés ne sont pas adaptés pour des items de grande taille car ils sont plus lents que Hasmap pour les actions d’ajout/suppression/recherche si on dispose d’un très grand nombre de données
  4. Evitez de créer des objets inutiles. N’allouez pas de la mémoire quand vous le pouvez, surtout pour les objets temporaires de courte durée. Le Garbage Collector sera minime quand peu d’objet sont créés.
  5. Vérifiez le heap disponible pour votre application. Invoquez « ActivityManager::getMemoryClass() » pour connaître la taille du heap (mp) disponible pour votre application. L’exception OutofMemory se déclenchera si vous essayer d’allouer une quantité de mémoire supérieure à celle disponible. Si votre application déclare un « largeHeap » dans le Manifeste AndroidManifest.xml, vous pouvez invoquez « ActivityManager::getLargeMemoryClass() » pour avoir une estimation de la taille d’un heap large.
  6. Faites la coordination avec le système en implémentant le callback « onTrimMemory ». Implémentez « ComponentCallbacks2::inTrimMemory(int) » dans votre Activité/Service/ContentProvider pour libérer graduellement de la mémoire conformément aux contraintes système. onTrimMemory(int) améliore la vitesse de réponse du système général, et prolonge également le cycle de vie du logiciel dans le système.

Quand TRIM_MEMORY_UI_HIDDEN se produit, cela signifie que toutes les UI dans votre application ont été cachés, et que vous devez libérer des ressources UI. Quand votre application est au premier plan, vous recevrez potentiellement TRIM_MEMORY_RUNNING[MODERATE/LOW/CRITICAL], ou vous pouvez recevoir TRIM_MEMORY_[BACKGROUND/MODERATE/COMPLETE] si votre application est en arrière-plan. Vous pouvez libérer des ressources non critiques selon la stratégie de vidage de mémoire quand votre mémoire système est stricte.

  1. Les services doivent être utilisés avec prudence. Si vous avez besoin d’un service pour exécuter un job en arrière-plan, éviter de les laisser en état d’exécution sauf s’ils effectuent une tâche. Essayer de raccourcir leurs cycles de vie en utilisant un IntentService, ce qui permettra à ce service de s’autoterminer à la fin de la prise en charge de cette intention. Les services doivent être utilisés avec prudence pour ne pas en laisser un qui fonctionne inutilement. Dans le pire des cas, les performances globales du systèmes seront réduites, et les utilisateurs désinstalleront votre application (S’ils peuvent le faire)
  2. Les librairies externes doivent être utilisées avec prudence. En effet, elle sont souvent conçues pour des devices qui ne sont pas mobiles, et peuvent donc être inefficaces sur Android. Vous devez prendre en considération l’effort de porter et d’optimiser la librairie pour mobile avant de prendre votre décision de l’utiliser. Si vous utiliser la librairies pour seulement une ou 2 fonctionnalités parmi les milliers proposées, il serait préférable que vous l’implémentiez vous-même.
  3. Utilisez des images bitmaps avec une résolution correcte. Chargez un bitmap avec la résolution dont vous avez besoin, ou réduisez cette résolution si celle du bitmap original est supérieur à vos besoins.
  4. Utilisez Proguard ou zipalign. L’outil Proguard supprime tout code inutilisé, rend les classes, les méthodes et les champs impénétrables, en utilisant l’obfuscation. Ceci compressera votre code et réduira les plages de RAM nécessaires à l’opération de mapping. L’outil zipalign réalignera votre APK. Si zipalign n’est pas en cours de fonctionnement, vous aurez besoin de beaucoup plus de mémoire, car les fichiers ressources ne pourront plus être mappés à partir de l’APK.
Comment éviter les fuites de mémoire

L’utilisation de la mémoire avec précaution en appliquant les astuces ci-dessous peut faire bénéficier vos applications de façon incrémentielle, et lui permet de rester plus longtemps dans le système. Mais ces bénéfices peuvent se perdre en cas de fuite de mémoire. Voici quelques failles potentielles que tout développeur doit garder en esprit :

  1. Fermez les curseurs après avoir adressé une requête à la base de données. Si vous voulez conserver le curseur pendant une longue, utilisez le avec prudence et fermez le dès que la tâche avec la base de données se termine.
  2. Faites un appel à la fonction unregisterReceiver() après la fonction registreReceiver()
  3. Evitez les fuites de contexte. Si vous déclarer une variable static member « Drawable » dans votre Activité, et faire l’appel view.setBackground(drawable) dans onCreate(), après une rotation d’écran, une nouvelle instance de l’activité sera créée et l’espace mémoire occupé par ancienne instance de Activité ne pourra pas être libéré, parce que Drawable a paramétré la vue (view) en tant que callback, tandis que view a une référence à Activité(context). Une instance Activité fuité implique un coût énorme en mémoire, ce qui risque de causer une OOM.

Voici 2 méthodes pour éviter ce type de fuites :

  • Ne gardez pas les références à context-activity pour une longue durée. Une référence à une activité doit avoir le même cycle de vie que l’activité elle-même
  • Essayez d’utiliser context-application au lieu de context-activity
  1. Eviter les classes inner non static dans une activité. Dans Java, les classes anon static anonymous possèdent une référence implicite pour leur classe enclosing. Si vous ne faites pas attention, le stockage de cette référence peut causer la rétention de l’activité, au lieu d’être traitée par le Garbage Collecter. Donc, au lieu de cela, utiliser une classe static inner pour créer une référence faible dans l’activité elle-même.
  2. Utilisez les threads avec précaution. Dans Java, les threads sont des racines de la Garbage Collection. Ceci dit, le Dalvik VM garde les références fortes pour tous les threads actifs au sein du système d’exécution. Par conséquent, les threads délaissés ne pourront jamais être éligibles à la Garbage Collection. Les threads Java persisteront jusqu’à l’extinction totale du processus par le système Android. A lieu de cela, le framework d’application Android fournit plusieurs classes conçues pour effectuer du threading en arrière plan plus facile pour les développeurs :
    • Utilisez le Loader à la place du thread en utilisant des requêtes d’arrière plan et de courte durée en adéquation avec le cycle de vie de l’Activité
    • Utilisez le services et ramener les résultats à l’Activité en utilisant un BroadcastReceiver
    • Utilisez AsyncTask pour les opérations de courte durée
Comment analyser l’utilisation de la mémoire

Pour en savoir plus sur les statistiques d’utilisation de mémoire en mode en ligne et hors ligne, vous pouvez vérifier Android main log en utilisant la commande logcat dans Android Debug Bridge (ADB), videz les informations de la mémoire pour un nom de package spécifique, ou utilisez d’autres outils tels le Dalvik Debug Monitor Server (DDMS) et Memory Analyzer Tool (MAT). Voici quelques introductions concises concernant les méthodes d’analyse de l’utilisation mémoire de votre application.

  1. Essayer de comprendre les messages du log de la Garbage Collection de la Dalvik VM, comme le montre l’exemple et définition suivantes :

tips-for-optimizing-android-app-memory-fig1-dalvik-virtual-machine-log

  • GC Reason : Qu’est ce qui a déclenché la Garbage Collection et de quel type de collections est-elle ? Les raisons peuvent inclure :
    • GC_CONCURRENT : Un Concurrent Garbage Collection qui libère la mémoire quand le heap commence à se remplir
    • GC_FOR_ALLOC : Cette Garbage Collection se déclenche après une tentative de l’application d’allouer de la mémoire alors que votre heap est plein, donc le système arrête l’application et réclame de la mémoire
    • GC_HPROF_DUMP_HEAP : Une Garbage Collection qui se déclenche quand vous créer un fichier HPROF pour analyser votre heap
    • GC_EXPLICIT : Une Garbage Collection explicite, pareille à celle qui vient suite à l’appel de gc() (Vous devez éviter de faire un appel à celle-ci et faire confiance au GC qui s’exécutera au besoin)
  • Quantité libérée : La quantité de mémoire réclamée par cette Garbage Collection
  • Statistiques Heap : Le pourcentage de mémoire libérée et (Nombre d’objets exécutés/taille totale du heap)
  • Statistiques de mémoire externe : La Mémoire externe allouée dans les API 10 et ses antécédents et le minimum entre (la quantité de mémoire allouée)/(Limite à laquelle la collection se déclenchera)
  • Temps de pause : Les heaps plus grands auront des pauses plus longues. Les temps de pause concurrentes nous montrent 2 pauses : l’une au début de la collection, l’autre à la fin.

Plus le log GC est large, plus nombreuses sont les opérations d’allocation/libération de mémoire, ce qui signifie un étranglement de l’expérience utilisateur.

  1. Utilisez DDMS pour afficher les mises à jour du heap et faire un suivi des allocations.

Il est pratique de vérifier les allocations du heap d’un processus spécifique en temps réel avec DDMS. Essayer d’interagir avec votre application et observer les mises à jour du heap de l’application dans l’onglet « Heap ». Ceci vous aidera à identifier laquelle des actions consomme trop de mémoire. L’onglet « Allocation Tracker » nous affiche toutes les allocations récentes, fournissant des informations qui incluent le type de l’objet, le thread dans lequel il est alloué, la classe, le fichier et dans quelle ligne. Pour plus d’informations concernant l’utilisation de DDMS pour l’analyse des heap, prière de visiter la section Références à la fin de cet article. La capture d’écran suivante nous montre un DDMS en cours d’exécution, incluant les processus actuels et les statistiques de mémoire heap pour chaque processus spécifique.

tips-for-optimizing-android-app-memory-fig2-ddms-heap-updates-track-allocation

  1. Observez les allocations mémoire globales.

En exécutant la commande adb « adb shell dumpsys meminfo <package_name », vous pouvez afficher toutes vos allocations d’application actuelles, en kilobytes.

tips-for-optimizing-android-app-memory-fig3-overall-memory-allocations

En général, ce sont les colonnes « Pss Total » et « Private Dirty » qui méritent notre attention. La colonne « PSS Total » inclut toute allocation Zygote (Mesurée par des processus partagés en croix, comme la décrit la définition du PSS au-dessus). La colonne « Private Dirty » représente en revanche la quantité de mémoire RAM affectée actuellement à la heap de votre applications, vos allocations, et toute page d’allocation Zygote qui a été modifié depuis la déviation de votre processus d’application de Zygote.

En plus, la « ViewRootImpl » nous montre le nombre de root views actifs dans votre processus. Chaque root view est associé à une fenêtre, ce qui vous aidera à identifier les fuites de mémoires impliquant des boîtes de dialogue et d’autres fenêtres. Les « AppContexts » et « Activities » montre le nombre de Context d’application et d’Objets d’Activity actifs dans votre processus. Ceci peut être très utile pour identifier de fuites d’objets d’Activity qui ne pourraient être collectées par le GC à cause de leurs références statiques, ce qui est commun. Ces objets ont souvent plusieurs autres allocations qui leurs sont associés et offrent une bonne occasion pour faire le suivi des grandes fuites de mémoire.

  1. Capturer un heap dump et analysez le avec le « Eclipse Memory Analyzer Tool (MAT) »

Vous pouvez capturer directement un heap dump en utilisation DDMS ou en appelant la fonction Debug::dumpHprofData() dans votre code source pour des résultats plus précis. Vous aurez ensuite besoin de l’outil hprof-conv pour générer le fichier HPROF converti. La capture d’écran suivante est un résultat d’analyse mémoire affiché dans le MAT.

tips-for-optimizing-android-app-memory-fig4-eclipse-memory-analyzer-tool

Résumé

Pour concevoir des applications memory friendly, les développeurs Android doivent connaître les ABC de la Gestion Mémoire sous Android. Les développeurs doivent apprendre à avoir une utilisation mémoire efficace, utiliser des outils d’analyse, et implémenter les astuces citées dans cet article. Concevoir des applications stables et évolutives dès le début vaut mieux que d’appliquer les correctifs durant la période d’implémentation.

AUCUN COMMENTAIRE

LAISSER UN COMMENTAIRE

five × 2 =