Bonnes pratiques pour gérer la mémoire

Avant de consulter ce document, nous vous recommandons de suivre les bonnes pratiques dédiées à la gestion de la mémoire pour les applications Android (par exemple, Gérer la mémoire de votre application).

Introduction

Une fuite de mémoire correspond à un type de fuite de ressource qui se produit lorsqu'un programme informatique ne libère pas de la mémoire allouée qui n'est plus utile. Suite à une fuite, l'application risque de demander à l'OS plus de mémoire que celle dont il dispose, entraînant le plantage de l'application. Un certain nombre de pratiques inappropriées peuvent générer des fuites de mémoire dans les applications Android (par exemple, lorsque les ressources ne sont pas gérées correctement ou lorsque les écouteurs qui ne sont plus utiles ne sont pas désenregistrés).

Ce document fournit quelques bonnes pratiques pour vous aider à éviter, détecter et résoudre les fuites de mémoire dans votre code. Si vous avez essayé les méthodes décrites dans ce document et que vous suspectez une fuite de mémoire dans nos SDK, consultez Signaler des problèmes avec les SDK Google.

Avant de contacter l'assistance

Avant de signaler une fuite de mémoire à l'équipe d'assistance Google, suivez les bonnes pratiques et la procédure de débogage indiquées dans ce document pour vous assurer que l'erreur ne provient pas de votre code. Si vous ne parvenez pas à résoudre le problème en suivant la procédure, celle-ci générera les informations dont l'équipe d'assistance Google a besoin pour vous aider.

Éviter les fuites de mémoire

Suivez ces bonnes pratiques pour vous aider à éviter certaines des causes les plus courantes de fuite de mémoire dans le code qui utilise les SDK Google.

Bonnes pratiques pour les applications Android

Assurez-vous d'effectuer toutes les actions suivantes dans votre application Android :

  1. Libérer les ressources inutilisées
  2. Désenregistrer les écouteurs qui ne sont plus utiles
  3. Annuler les tâches qui ne sont plus utiles
  4. Transférer les méthodes du cycle de vie pour libérer des ressources
  5. Utiliser les dernières versions des SDK

Pour en savoir plus sur chacune de ces pratiques, consultez les sections suivantes.

Libérer les ressources inutilisées

Si votre application Android utilise une ressource, veillez à la libérer lorsqu'elle n'est plus nécessaire. Si vous ne le faites pas, la ressource continue d'utiliser de la mémoire alors que votre application n'en a plus besoin. Pour en savoir plus, consultez Cycle de vie d'une activité dans la documentation Android.

Libérer les références GoogleMap non actualisées dans GeoSDKs

Il arrive souvent qu'une GoogleMap entraîne une fuite de mémoire si elle est mise en cache à l'aide de NavigationView ou MapView. Une GoogleMap a une relation un à un avec la NavigationView ou la MapView de laquelle elle est extraite. Vous devez vous assurer que la GoogleMap n'est pas mise en cache ou que la référence est libérée lorsque NavigationView#onDestroy ou MapView#onDestroy sont appelés. Si vous utilisez le NavigationSupportFragment, le MapSupportFragment ou votre propre fragment qui encapsule ces vues, la référence doit être libérée dans Fragment#onDestroyView.

class NavFragment : SupportNavigationFragment() {

  var googleMap: GoogleMap?

  override fun onCreateView(
    inflater: LayoutInflater,
    parent: ViewGroup?,
    savedInstanceState: Bundle?,
  ): View  {
    super.onCreateView(inflater,parent,savedInstanceState)
    getMapAsync{map -> googleMap = map}
  }

  override fun onDestroyView() {
    googleMap = null
  }
}

Désenregistrer les écouteurs qui ne sont plus utiles

Si votre application Android enregistre un écouteur pour un événement (par exemple, un clic sur un bouton ou le changement d'état d'une vue), veillez à désenregistrer cet écouteur lorsque l'application n'a plus besoin de surveiller l'événement. Si vous ne le faites pas, l'écouteur continue d'utiliser de la mémoire alors que votre application n'en a plus besoin.

Par exemple, imaginons que votre application utilise le SDK Navigation et qu'elle appelle la méthode addArrivalListener pour écouter les événements d'arrivée. Elle devra également appeler removeArrivalListener lorsqu'elle n'aura plus besoin de surveiller ces événements.

var arrivalListener: Navigator.ArrivalListener? = null

fun registerNavigationListeners() {
  arrivalListener =
    Navigator.ArrivalListener {
      ...
    }
  navigator.addArrivalListener(arrivalListener)
}

override fun onDestroy() {
  navView.onDestroy()
  if (arrivalListener != null) {
    navigator.removeArrivalListener(arrivalListener)
  }

  ...
  super.onDestroy()
}

Annuler les tâches qui ne sont plus utiles

Lorsqu'une application Android commence une tâche asynchrone (comme un téléchargement ou une requête réseau), assurez-vous de l'annuler une fois qu'elle est terminée. Si vous ne le faites pas, la tâche continuera de s'exécuter en arrière-plan même une fois que l'application aura cessé de l'utiliser.

Pour en savoir plus sur ces bonnes pratiques, consultez Gérer la mémoire de votre application dans la documentation Android.

Transférer les méthodes du cycle de vie pour libérer des ressources

Si votre application utilise le SDK Navigation ou Maps, assurez-vous de libérer les ressources en transférant les méthodes de cycle de vie (affichées en gras) vers navView. Pour ce faire, vous pouvez utiliser NavigationView dans le SDK Navigation ou MapView dans le SDK Maps ou Navigation. Vous pouvez aussi utiliser SupportNavigationFragment ou SupportMapFragment au lieu d'utiliser directement NavigationView et MapView respectivement. Les fragments Support gèrent le transfert des méthodes de cycle de vie.

class NavViewActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    ...
    navView = ...
    navView.onCreate(savedInstanceState)
    ...
  }

  override fun onSaveInstanceState(savedInstanceState: Bundle) {
    super.onSaveInstanceState(savedInstanceState)
    navView.onSaveInstanceState(savedInstanceState)
  }

  override fun onTrimMemory(level: Int) {
    super.onTrimMemory(level)
    navView.onTrimMemory(level)
  }

  /* Same with
    override fun onStart()
    override fun onResume()
    override fun onPause()
    override fun onConfigurationChanged(...)
    override fun onStop()
    override fun onDestroy()
  */
}

Utiliser les dernières versions des SDK

Les SDK Google sont mis à jour constamment avec de nouvelles fonctionnalités, des corrections de bugs et des améliorations des performances. Pour profiter de ces corrections, actualisez régulièrement les SDK dans votre application.

Déboguer des fuites de mémoire

Si vous voyez toujours des fuites de mémoire après avoir appliqué toutes les suggestions pertinentes décrites ci-dessus, suivez cette procédure pour les déboguer.

Avant de commencer, vous devez savoir comment Android gère la mémoire. Pour en savoir plus, consultez Présentation de la gestion de mémoire dans la documentation Android.

Pour déboguer une fuite de mémoire, procédez comme suit :

  1. Reproduisez le problème. Cette étape est essentielle pour le débogage.
  2. Déterminez si l'utilisation de la mémoire est conforme aux attentes. Vérifiez si l'augmentation de l'utilisation qui semble correspondre à une fuite ne représente pas en réalité la mémoire requise pour exécuter votre application.
  3. Effectuez un débogage général. Pour cela vous pouvez utiliser plusieurs utilitaires. Trois ensembles d'outils standards peuvent vous aider à déboguer les problèmes de mémoire dans Android : Android Studio, Perfetto et les utilitaires de ligne de commande Android Debug Bridge (adb).
  4. Vérifiez l'utilisation de la mémoire de votre application. Obtenez une empreinte de la mémoire et un suivi de l'allocation, puis analysez-les.
  5. Corrigez les fuites de mémoire.

Les sections suivantes décrivent ces étapes en détail.

Étape 1 : Reproduisez le problème

Si vous n'avez pas pu reproduire le problème, commencez par réfléchir aux scénarios susceptibles d'avoir généré la fuite de mémoire. Si vous savez que le problème a été reproduit, vous pouvez essayer d'examiner directement une empreinte de la mémoire. Toutefois, si vous n'obtenez une empreinte de la mémoire que lors du démarrage de l'application ou à un autre moment aléatoire, vous n'avez sans doute pas activé les conditions pour déclencher une fuite. Envisagez d'utiliser plusieurs scénarios lorsque vous essayez de reproduire le problème :

  • Quels ensembles de fonctionnalités sont activés ?

  • Quelle séquence spécifique d'actions des utilisateurs déclenche la fuite ?

    • Avez-vous essayé plusieurs itérations d'activation de cette séquence ?
  • Par quels états du cycle de vie l'application est-elle passée ?

    • Avez-vous essayé plusieurs itérations avec différents états de cycle de vie ?

Vérifiez si vous pouvez reproduire le problème dans la dernière version des SDK. Si le problème provient d'une version précédente, il a peut-être déjà été corrigé.

Étape 2 : Vérifiez si l'utilisation de la mémoire par l'application est conforme aux attentes

Toute fonctionnalité nécessite de la mémoire supplémentaire. Lorsque vous déboguez différents scénarios, déterminez si l'utilisation de la mémoire correspond à ce qui était prévu ou s'il s'agit réellement d'une fuite de mémoire. Par exemple, pour différentes fonctionnalités ou tâches d'utilisateur, envisagez les possibilités suivantes :

  • Fuite probable : l'activation du scénario avec plusieurs itérations entraîne une augmentation de l'utilisation de la mémoire au fil du temps.

  • Utilisation de la mémoire prévue probable : la mémoire est récupérée lorsque le scénario s'arrête.

  • Utilisation de la mémoire prévue possible : l'utilisation de la mémoire augmente pendant un certain temps, puis diminue. Cela peut être dû à un cache limité ou à une utilisation de mémoire attendue.

Si le comportement de l'application correspond à "Utilisation de la mémoire prévue probable", vous pouvez résoudre le problème en gérant la mémoire de votre application. Pour obtenir de l'aide, consultez Gérer la mémoire de votre application.

Étape 3 : Effectuez un débogage général

Lorsque vous déboguez une fuite de mémoire, commencez par un débogage général, puis allez plus dans le détail une fois que vous avez affiné les options possibles. Commencez par utiliser l'un de ces outils de débogage général pour déterminer s'il existe une fuite au fil du temps :

Profileur de mémoire Android Studio

Cet outil fournit un histogramme visuel de la mémoire consommée. Vous pouvez aussi déclencher les empreintes de la mémoire et le suivi de l'allocation à partir de cette même interface. Cet outil est recommandé par défaut. Pour en savoir plus, consultez Profileur de mémoire Android Studio.

Compteurs de mémoire Perfetto

Perfetto vous permet de contrôler avec précision le suivi de plusieurs métriques et les présente toutes dans un histogramme unique. Pour en savoir plus, consultez Compteurs de mémoire Perfetto.

Interface utilisateur Perfetto

Utilitaires de ligne de commande Android Debug Bridge (adb)

La plupart des éléments que vous pouvez suivre avec Perfetto sont également disponibles sous forme d'utilitaire de ligne de commande adb que vous pouvez interroger directement. Voici deux exemples intéressants :

  • Meminfo vous permet de consulter des informations détaillées sur la mémoire à un moment précis.

  • Procstats fournit des statistiques cumulées importantes au fil du temps.

Il est essentiel d'examiner la statistique sur l'espace mémoire physique maximal (maxRSS) dont l'application a besoin au fil du temps. Il est possible que MaxPSS ne soit pas aussi précis. Pour savoir comment améliorer la précision, consultez l'option adb shell dumpsys procstats --help –start-testing.

Suivi de l'allocation

Le suivi de l'allocation identifie la trace de la pile où la mémoire est allouée et détermine si elle n'a pas été libérée. Cette étape est particulièrement utile lorsque vous localisez des fuites dans le code natif. Comme cet outil identifie la trace de la pile, il peut être très efficace pour déboguer rapidement la cause ou pour déterminer comment reproduire le problème. Pour savoir comment utiliser le suivi de l'allocation, consultez Déboguer les problèmes de mémoire dans le code natif grâce au suivi de l'allocation.

Étape 4 : Vérifiez l'utilisation de la mémoire de votre application avec une empreinte de la mémoire

Pour détecter une fuite de mémoire, vous pouvez par exemple obtenir une empreinte de la mémoire de votre application, puis l'inspecter à la recherche de fuites. Une empreinte de la mémoire est un instantané de tous les objets contenus dans la mémoire d'une application. Elle peut vous permettre de diagnostiquer des fuites de mémoire et d'autres problèmes liés à la mémoire.

Android Studio peut détecter les fuites de mémoire qui ne peuvent pas être corrigées par le RM. Lorsque vous capturez une empreinte de la mémoire, Android Studio vérifie si une activité ou un fragment sont encore accessibles alors qu'ils ont déjà été détruits.

  1. Capturez une empreinte de la mémoire.
  2. Analysez l'empreinte de la mémoire pour détecter les fuites de mémoire.
  3. Corrigez les fuites de mémoire.

Pour en savoir plus, consultez les sections suivantes.

Capturer une empreinte de la mémoire

Pour capturer une empreinte de la mémoire, vous pouvez utiliser Android Debug Bridge (adb) ou le profileur de mémoire Android Studio.

Utiliser adb pour capturer une empreinte de la mémoire

Pour capturer une empreinte de la mémoire à l'aide d'adb :

  1. Connectez votre appareil Android à votre ordinateur.
  2. Ouvrez une invite de commande et accédez au répertoire où se trouvent les outils adb.
  3. Pour capturer une empreinte de la mémoire, exécutez cette commande :

    adb shell am dumpheap my.app.name $PHONE_FILE_OUT

  4. Pour récupérer l'empreinte de la mémoire, exécutez cette commande :

    adb pull $PHONE_FILE_OUT $LOCAL_FILE.

Utiliser Android Studio pour capturer une empreinte de la mémoire

Pour capturer une empreinte de la mémoire à l'aide du profileur de mémoire Android Studio, suivez cette procédure de la section Capturer une empreinte de la mémoire, dans la documentation Android.

Analyser l'empreinte de la mémoire pour détecter les fuites de mémoire

Une fois que vous avez capturé une empreinte de la mémoire, vous pouvez utiliser le profileur de mémoire Android Studio pour l'analyser. Voici comment procéder :

  1. Ouvrez votre projet Android dans Android Studio.

  2. Sélectionnez Run (Exécuter), puis la configuration Debug (Débogage).

  3. Ouvrez l'onglet Android Profiler (Profileur Android).

  4. Sélectionnez Memory (Mémoire).

  5. Sélectionnez Open heap dump (Ouvrir l'empreinte de la mémoire), puis le fichier d'empreinte de la mémoire que vous avez généré. Le profileur de mémoire affiche un graphique d'utilisation de la mémoire de votre application.

  6. Utilisez-le pour analyser l'empreinte de la mémoire :

    • Identifiez les objets qui ne sont plus utilisés.

    • Identifiez les objets qui utilisent beaucoup de mémoire.

    • Déterminez la quantité de mémoire utilisée par chaque objet.

  7. Utilisez ces informations pour affiner votre analyse ou pour trouver la source de la fuite de mémoire et la corriger.

Étape 5 : Corrigez les fuites de mémoire

Une fois que vous avez identifié la source de la fuite de mémoire, vous pouvez la corriger. Corriger les fuites de mémoire dans vos applications Android vous aide à améliorer les performances et la stabilité de vos applications. Les détails varient selon les scénarios, mais les suggestions suivantes peuvent être utiles :

Autres outils de débogage

Si vous n'avez pas réussi à identifier ni à corriger la fuite de mémoire, essayez ces outils :

Déboguer les problèmes de mémoire dans le code natif grâce au suivi de l'allocation

Même si vous n'utilisez pas directement du code natif, plusieurs bibliothèques Android le font, y compris les SDK Google. Si vous pensez que la fuite de mémoire est en code natif, vous pouvez utiliser plusieurs outils pour la déboguer. Que ce soit avec Android Studio ou heapprofd (également compatible avec Perfetto), le suivi de l'allocation est un excellent moyen d'identifier les causes potentielles d'une fuite de mémoire. De plus, c'est souvent la méthode la plus rapide pour la déboguer.

Le suivi de l'allocation offre un autre avantage spécifique : il vous permet de partager les résultats sans inclure les informations sensibles qui figurent dans un tas de mémoire.

Identifier les fuites avec LeakCanary

LeakCanary est un outil efficace pour identifier les fuites de mémoire dans les applications Android. Pour savoir comment utiliser LeakCanary dans votre application, accédez à LeakCanary.

Signaler des problèmes avec les SDK Google

Si vous avez essayé les méthodes décrites dans ce document et que vous suspectez une fuite de mémoire dans nos SDK, contactez le service client en fournissant un maximum des informations suivantes :

  • Les étapes reproduisant la fuite de mémoire : si elles nécessitent un codage complexe, il peut être utile de copier le code reproduisant le problème dans notre application exemple et d'indiquer les étapes supplémentaires à effectuer dans l'UI pour déclencher la fuite.

  • Les empreintes de mémoire capturées depuis votre application en reproduisant le problème : capturez les empreintes de mémoire à deux moments différents où l'utilisation de la mémoire a fortement augmenté.

  • Si une fuite de mémoire native est prévue, partagez le résultat du suivi de l'allocation issu de heapprofd.

  • Un rapport de bug généré après avoir reproduit la fuite.

  • Des traces de pile pour tout plantage lié à la mémoire.

    Remarque importante : Comme les traces de pile ne sont généralement pas suffisantes pour déboguer un problème de mémoire, assurez-vous de fournir également l'un des autres formulaires d'informations.