Cette page explique comment réduire de manière proactive l'utilisation de la mémoire dans votre application. Pour savoir comment le système d'exploitation Android gère la mémoire, consultez la présentation de la gestion de mémoire Android.
La mémoire vive (RAM) est une ressource précieuse dans tout environnement de développement logiciel et elle est encore plus utile sur un système d'exploitation mobile où la mémoire physique est souvent limitée.
Bien qu'Android Runtime (ART) et la machine virtuelle Dalvik effectuent tous les deux une récupération de mémoire de routine, cela ne signifie pas que vous pouvez ignorer quand et où votre application alloue et libère de la mémoire.
Vous devez toujours éviter les fuites de mémoire, généralement causées par la conservation de références d'objets dans des variables de membre statiques, et libérer des objets Reference
au moment approprié, tels que définis par des rappels de cycle de vie.
Surveiller la mémoire disponible et l'utilisation de la mémoire
Vous devez identifier les problèmes d'utilisation de mémoire de votre application avant de pouvoir les résoudre. Le Profileur de mémoire d'Android Studio vous aide à identifier et à diagnostiquer les problèmes de mémoire de différentes manières :
- Découvrez comment votre application alloue de la mémoire au fil du temps. Le Profileur de mémoire affiche un graphique en temps réel de la quantité de mémoire utilisée par votre application, du nombre d'objets Java alloués et de la récupération de mémoire.
- Lancez des événements de récupération de mémoire et prenez un instantané du tas de mémoire Java pendant l'exécution de votre application.
- Enregistrez les allocations de mémoire de votre application, inspectez tous les objets alloués, affichez la trace de la pile pour chaque allocation et accédez au code correspondant dans l'éditeur Android Studio.
Libérer la mémoire en fonction d'événements
Android peut récupérer la mémoire de votre application ou l'arrêter complètement si nécessaire afin de libérer de la mémoire pour les tâches critiques, comme expliqué dans la présentation de la gestion de mémoire. Pour équilibrer davantage la mémoire système et éviter d'arrêter le processus de votre application comme le requiert le système, vous pouvez intégrer l'interface ComponentCallbacks2
dans vos classes Activity
.
La valeur fournie
onTrimMemory()
informe votre application des événements liés au cycle de vie ou à la mémoire qui présentent une bonne
l'opportunité pour votre application de réduire volontairement l'utilisation de sa mémoire. Libérer de la mémoire peut réduire
la probabilité que votre application
soit tuée par le
tueur de mémoire faible.
Vous pouvez implémenter le rappel onTrimMemory()
pour répondre à différents événements liés à la mémoire, comme illustré dans l'exemple suivant :
Kotlin
import android.content.ComponentCallbacks2 // Other import statements. class MainActivity : AppCompatActivity(), ComponentCallbacks2 { // Other activity code. /** * Release memory when the UI becomes hidden or when system resources become low. * @param level the memory-related event that is raised. */ override fun onTrimMemory(level: Int) { if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) { // Release memory related to UI elements, such as bitmap caches. } if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) { // Release memory related to background processing, such as by // closing a database connection. } } }
Java
import android.content.ComponentCallbacks2; // Other import statements. public class MainActivity extends AppCompatActivity implements ComponentCallbacks2 { // Other activity code. /** * Release memory when the UI becomes hidden or when system resources become low. * @param level the memory-related event that is raised. */ public void onTrimMemory(int level) { if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) { // Release memory related to UI elements, such as bitmap caches. } if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) { // Release memory related to background processing, such as by // closing a database connection. } } }
Vérifier la quantité de mémoire dont vous avez besoin
Pour autoriser plusieurs processus en cours d'exécution, Android définit une limite stricte sur la taille du tas de mémoire alloué à chaque application. La limite exacte de cette taille varie selon les appareils, en fonction de la quantité de RAM dont ils disposent. Si votre application atteint sa capacité de tas de mémoire et tente d'allouer plus de mémoire, le système génère une erreur OutOfMemoryError
.
Pour éviter de manquer de mémoire, vous pouvez interroger le système afin de déterminer l'espace disponible sur l'appareil actuel pour le tas de mémoire. Dans ce cas, vous pouvez interroger le système en appelant getMemoryInfo()
.
Cette opération renvoie un objet ActivityManager.MemoryInfo
qui fournit des informations sur l'état de la mémoire actuelle de l'appareil, y compris la mémoire disponible, la mémoire totale et le seuil de mémoire (niveau de mémoire à partir duquel le système se met à arrêter les processus). L'objet ActivityManager.MemoryInfo
expose également lowMemory
, qui est une valeur booléenne simple qui vous indique si l'appareil manque de mémoire.
L'exemple d'extrait de code suivant montre comment utiliser la méthode getMemoryInfo()
dans votre application.
Kotlin
fun doSomethingMemoryIntensive() { // Before doing something that requires a lot of memory, // check whether the device is in a low memory state. if (!getAvailableMemory().lowMemory) { // Do memory intensive work. } } // Get a MemoryInfo object for the device's current memory status. private fun getAvailableMemory(): ActivityManager.MemoryInfo { val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager return ActivityManager.MemoryInfo().also { memoryInfo -> activityManager.getMemoryInfo(memoryInfo) } }
Java
public void doSomethingMemoryIntensive() { // Before doing something that requires a lot of memory, // check whether the device is in a low memory state. ActivityManager.MemoryInfo memoryInfo = getAvailableMemory(); if (!memoryInfo.lowMemory) { // Do memory intensive work. } } // Get a MemoryInfo object for the device's current memory status. private ActivityManager.MemoryInfo getAvailableMemory() { ActivityManager activityManager = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE); ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo(); activityManager.getMemoryInfo(memoryInfo); return memoryInfo; }
Utiliser des constructions de code plus efficaces pour la mémoire
Certaines fonctionnalités Android, classes Java et constructions de code peuvent utiliser plus de mémoire que d'autres. Vous pouvez réduire la quantité de mémoire utilisée par votre application en choisissant des alternatives plus efficaces dans votre code.
Utiliser les services avec parcimonie
Nous vous recommandons vivement de ne pas laisser les services s'exécuter lorsque ce n'est pas nécessaire. Laisser les services s'exécuter inutilement est l'une des pires erreurs de gestion de la mémoire qu'une application Android puisse commettre. Si votre application a besoin d'un service pour fonctionner en arrière-plan, ne le laissez s'exécuter que s'il doit exécuter une tâche. Arrêtez le service lorsqu'il a terminé sa tâche. Sinon, vous risquez de provoquer une fuite de mémoire.
Lorsque vous démarrez un service, le système préfère continuer à exécuter le processus correspondant. Ce comportement rend les processus des services très gourmands, car la RAM utilisée par un service reste indisponible pour les autres processus. Cela réduit le nombre de processus mis en cache que le système peut conserver dans le cache LRU, ce qui rend le changement d'application moins efficace. Cela peut même entraîner un thrashing dans le système lorsque la mémoire est insuffisante et que le système ne peut gérer suffisamment de processus pour héberger tous les services en cours d'exécution.
En règle générale, évitez d'utiliser des services persistants, car ils demandent en permanence de la mémoire disponible. Utilisez plutôt une autre intégration, telle que WorkManager
. Pour en savoir plus sur l'utilisation de WorkManager
pour planifier des processus en arrière-plan, consultez la section Tâches persistantes.
Utiliser des conteneurs de données optimisés
Certaines classes fournies par le langage de programmation ne sont pas optimisées pour les appareils mobiles. Par exemple, l'intégration HashMap
générique peut se révéler inefficace en mémoire, car elle a besoin d'un objet d'entrée distinct pour chaque mise en correspondance.
Le framework Android inclut plusieurs conteneurs de données optimisés, parmi lesquels SparseArray
, SparseBooleanArray
et LongSparseArray
.
Par exemple, les classes SparseArray
sont plus efficaces, car elles évitent au système de compléter automatiquement la clé et parfois une valeur (ce qui crée un ou deux objet(s) de plus par entrée).
Si nécessaire, vous pouvez toujours basculer vers des tableaux bruts pour bénéficier d'une structure de données synthétique.
Attention aux abstractions de code
Les développeurs utilisent souvent les abstractions en tant que bonne pratique de programmation, car elles leur permettent d'améliorer la flexibilité et la maintenance du code. Cependant, les abstractions sont beaucoup plus coûteuses, car elles nécessitent généralement plus de code à exécuter, ce qui nécessite plus de temps et de RAM pour mapper le code en mémoire. Si vos abstractions ne sont pas vraiment bénéfiques, évitez-les.
Utiliser des tampons de protocole allégés pour les données sérialisées
Les tampons de protocole sont un mécanisme extensible indépendant du langage et de la plate-forme, conçu par Google pour sérialiser des données structurées. Semblables au format XML, ils sont plus légers, plus simples et plus rapides. Si vous utilisez des tampons de protocole pour vos données, utilisez toujours des versions allégées dans votre code côté client. Les tampons de protocole classiques génèrent un code extrêmement détaillé, ce qui peut entraîner de nombreux problèmes dans votre application, tels qu'une utilisation accrue de la RAM, une augmentation significative de la taille des APK et un ralentissement de l'exécution.
Pour en savoir plus, consultez le fichier README.
Éviter la saturation de la mémoire
Les événements de récupération de mémoire n'affectent pas les performances de votre application. Cependant, une accumulation d'événements de récupération de mémoire en peu de temps peut rapidement décharger la batterie et augmenter légèrement le temps de configuration des frames en raison des interactions nécessaires entre le récupérateur de mémoire et les threads d'application. Plus le système se consacre à la récupération de mémoire, plus la batterie se décharge vite.
Souvent, les saturations de la mémoire peuvent entraîner une multiplication d'événements de récupération de mémoire. En pratique, une saturation de la mémoire décrit le nombre d'objets temporaires alloués dans un certain laps de temps.
Par exemple, vous pouvez allouer plusieurs objets temporaires dans une boucle for
. Vous pouvez également créer de nouveaux objets Paint
ou Bitmap
dans la fonction onDraw()
d'une vue. Dans les deux cas, l'application crée beaucoup d'objets rapidement et à grande échelle. Celles-ci peuvent rapidement utiliser toute la mémoire disponible des appareils de dernière génération, ce qui provoque un événement de récupération de mémoire.
Utilisez le Profileur de mémoire pour identifier les endroits de votre code où la saturation de la mémoire est élevée avant de pouvoir les corriger.
Une fois que vous avez localisé les zones problématiques dans votre code, essayez d'y réduire le nombre d'allocations. Envisagez de déplacer les éléments depuis des boucles internes ou éventuellement de les déplacer vers une structure d'allocation basée sur une fabrique.
Vous pouvez également déterminer si les pools d'objets profitent de ce cas d'utilisation. Avec un pool d'objets, au lieu de laisser tomber une instance d'objet, vous la libérez dans un pool lorsqu'elle n'est plus nécessaire. La prochaine fois qu'une instance d'objet de ce type sera nécessaire, vous pourrez l'acquérir à partir du pool plutôt que de l'allouer.
Évaluez méticuleusement les performances pour déterminer si un pool d'objets convient à une situation donnée. Dans certains cas, les pools d'objets peuvent nuire aux performances. Même si les pools évitent les allocations, ils entraînent d'autres frais généraux. Par exemple, la maintenance du pool implique généralement une synchronisation qui a des coûts non négligeables. De plus, la suppression de l'instance d'objet mis en pool pour éviter les fuites de mémoire pendant la sortie, puis son initialisation lors de l'acquisition peuvent entraîner des frais généraux.
Le fait de bloquer plus d'instances d'objets que nécessaire dans le pool alourdit la charge de récupération de mémoire. Bien que les pools d'objets réduisent le nombre d'appels de récupération de mémoire, ils augmentent la quantité de travail nécessaire pour chaque appel, car elle est proportionnelle au nombre d'octets actifs (accessibles).
Supprimer les ressources et les bibliothèques utilisant beaucoup de mémoire
Certaines ressources et bibliothèques de votre code peuvent utiliser de la mémoire à votre insu. La taille globale de votre application, y compris les bibliothèques tierces ou les ressources intégrées, peut affecter la quantité de mémoire utilisée par votre application. Vous pouvez améliorer l'utilisation de la mémoire de votre application en supprimant de votre code tous les composants, ressources ou bibliothèques inutiles, redondants ou superflus.
Réduire la taille globale de l'APK
Vous pouvez réduire de manière significative l'utilisation de la mémoire de votre application en diminuant la taille globale de celle-ci. La taille du bitmap, les ressources, les frames d'animation et les bibliothèques tierces peuvent tous augmenter la taille de votre application. Android Studio et le SDK Android fournissent plusieurs outils pour vous aider à réduire la taille de vos ressources et des dépendances externes. Ces outils sont compatibles avec les méthodes modernes de minification de code, telles que la compilation R8.
Pour savoir comment réduire la taille globale de votre application, consultez la section Réduire la taille de votre application.
Injection de dépendances avec Hilt ou Dagger 2
Les frameworks d'injection de dépendances peuvent simplifier le code que vous écrivez et fournir un environnement adaptatif utile pour tester et modifier la configuration.
Si vous avez l'intention d'utiliser un framework d'injection de dépendances dans votre application, envisagez d'utiliser Hilt ou Dagger. Hilt est une bibliothèque d'injection de dépendances pour Android qui s'exécute sur Dagger. Dagger n'utilise pas la réflexion pour scanner le code de votre application. Vous pouvez utiliser l'implémentation statique du temps de compilation de Dagger dans des applications Android sans coûts d'exécution ni utilisation de mémoire inutiles.
D'autres frameworks d'injection de dépendances qui utilisent la réflexion initialisent des processus en scannant votre code à la recherche d'annotations. Ce processus peut nécessiter beaucoup plus de cycles de processeur et de mémoire RAM, et entraîner un retard notable au lancement de l'application.
Attention aux bibliothèques externes
Souvent, le code de bibliothèque externe n'est pas écrit pour les environnements mobiles et il peut être inefficace lorsqu'il est utilisé sur un client mobile. Lorsque vous utilisez une bibliothèque externe, vous devrez peut-être l'optimiser pour les appareils mobiles. Planifiez ce travail et analysez la bibliothèque en termes de taille de code et d'empreinte RAM avant de l'utiliser.
Même certaines bibliothèques optimisées pour mobiles peuvent poser problème en raison de leurs différentes intégrations. Par exemple, une bibliothèque peut utiliser des tampons de mémoire allégés rapides, tandis qu'une autre utilise des tampons de mémoire micro, ce qui entraîne deux intégrations de tampons différentes dans votre application. Cela peut se produire avec différentes intégrations de journalisation, d'analyse, de frameworks de chargement d'images, de mise en cache, et de bien d'autres éléments inattendus.
Bien que ProGuard puisse vous aider à supprimer les API et les ressources avec les indicateurs appropriés, il ne peut supprimer les grandes dépendances internes d'une bibliothèque. Les fonctionnalités que vous souhaitez dans ces bibliothèques peuvent nécessiter des dépendances de niveau inférieur. Cela devient particulièrement problématique lorsque vous utilisez une sous-classe Activity
à partir d'une bibliothèque (qui peut avoir de nombreuses dépendances) lorsque les bibliothèques utilisent la réflexion, ce qui est courant et nécessite d'ajuster manuellement ProGuard pour qu'il fonctionne.
Évitez d'utiliser une bibliothèque partagée pour seulement une ou deux fonctionnalités sur des dizaines. N'importez pas une quantité importante de code et de frais généraux que vous n'utilisez pas. Lorsque vous envisagez d'utiliser une bibliothèque, recherchez l'intégration qui correspond le mieux à vos besoins. Sinon, créez votre propre intégration.