Optimiser l'utilisation de la mémoire

L'optimisation de la mémoire est essentielle pour garantir des performances fluides, éviter les plantages d'applications et maintenir la stabilité du système et l'intégrité de la plate-forme. Bien que l'utilisation de la mémoire doive être surveillée et optimisée dans chaque application, les applications de contenu pour les appareils TV présentent des défis spécifiques qui diffèrent des applications Android classiques pour les appareils mobiles.

Une consommation élevée de mémoire peut entraîner des problèmes de comportement des applications et du système, y compris :

  • L'application elle-même peut devenir lente ou saccadée, ou, dans le pire des cas, être arrêtée.
  • Les services système visibles par l'utilisateur (contrôle du volume, tableau de bord des paramètres d'image, Assistant vocal, etc.) deviennent très lents ou peuvent ne plus fonctionner du tout.
  • Le processus du démon LMK (Low Memory Killer) peut réagir à une forte pression sur la mémoire en arrêtant les processus les moins essentiels. Ces composants peuvent ensuite redémarrer peu de temps après, ce qui déclenche des pics de contention des ressources supplémentaires qui peuvent avoir un impact direct sur l'application au premier plan.
  • La transition vers le lanceur d'applications peut être considérablement retardée et laisser l'application au premier plan sembler ne pas répondre jusqu'à la fin de la transition.
  • Le système peut commencer à utiliser la récupération directe, en mettant temporairement en pause l'exécution d'un thread en attendant l'allocation de mémoire. Cela peut se produire sur n'importe quel thread, tel que le thread principal ou les threads liés au codec, ce qui peut entraîner des pertes de frames audio et vidéo, ainsi que des problèmes d'UI.

Considérations concernant la mémoire sur les appareils TV

Les téléviseurs disposent généralement de beaucoup moins de mémoire que les téléphones ou les tablettes. Par exemple, une configuration que nous pouvons voir sur un téléviseur est de 1 Go de RAM et une résolution vidéo de 1080p. En même temps, la plupart des applications TV ont des fonctionnalités similaires, ce qui implique une implémentation et des défis communs. Ces deux situations posent des problèmes qui ne se produisent pas avec d'autres types d'appareils et d'applications :

  • Les applications TV multimédias sont généralement composées de vues d'images en grille et d'images d'arrière-plan en plein écran, ce qui nécessite de charger de nombreuses images en mémoire en peu de temps.
  • Les applications TV lisent des flux multimédias qui nécessitent d'allouer une certaine quantité de mémoire pour lire des vidéos et de l'audio, et ont besoin de tampons multimédias considérables pour assurer une lecture fluide.
  • Les fonctionnalités multimédias supplémentaires (recherche, changement d'épisode, changement de piste audio, etc.) peuvent exercer une pression supplémentaire sur la mémoire si elles ne sont pas implémentées correctement.

Comprendre les téléviseurs

Ce guide se concentre principalement sur l'utilisation de la mémoire par les applications et sur les cibles de mémoire pour les appareils à faible RAM.

Sur les appareils TV, tenez compte des caractéristiques suivantes :

  • Mémoire de l'appareil : quantité de mémoire vive (RAM) installée sur l'appareil.
  • Résolution de l'UI de l'appareil : résolution utilisée par l'appareil pour afficher l'UI de l'OS et des applications. Elle est généralement inférieure à la résolution vidéo de l'appareil.
  • Résolution vidéo : résolution maximale à laquelle l'appareil peut lire les vidéos.

Cela conduit à classer différents types d'appareils et à déterminer comment la mémoire doit être utilisée par chacun d'eux.

Récapitulatif des appareils TV

Mémoire de l'appareil Résolution vidéo de l'appareil Résolution de l'UI de l'appareil isLowRAMDevice()
1 Go 1080p 720p Oui
1,5 Go 2160p 1080p Oui
≥ 1,5 Go 1080p 720p ou 1080p Non*
≥ 2 Go 2160p 1080p Non*

Appareils TV avec peu de RAM

Ces appareils sont dans une situation de mémoire limitée et signaleront ActivityManager.isLowRAMDevice() sur "true". Les applications qui s'exécutent sur des téléviseurs à faible RAM doivent mettre en œuvre des mesures de contrôle de la mémoire supplémentaires.

Nous considérons que les appareils présentant les caractéristiques suivantes appartiennent à cette catégorie :

  • Appareils de 1 Go : 1 Go de RAM, résolution de l'interface utilisateur de 720p/HD (1 280 x 720), résolution vidéo de 1080p/FullHD (1 920 x 1 080)
  • Appareils de 1,5 Go : 1,5 Go de RAM, résolution de l'interface utilisateur 1080p/FullHD (1 920 x 1 080), résolution vidéo 2160p/UltraHD/4K (3 840 x 2 160)
  • Autres situations dans lesquelles l'OEM a défini l'indicateur ActivityManager.isLowRAMDevice() en raison de contraintes de mémoire supplémentaires.

Téléviseurs classiques

Ces appareils ne sont pas soumis à une pression de mémoire aussi importante. Nous considérons que ces appareils présentent les caractéristiques suivantes :

  • ≥ 1,5 Go de RAM, interface utilisateur 720p ou 1080p et résolution vidéo 1080p
  • ≥ 2 Go de RAM, interface utilisateur 1080p et résolution vidéo 1080p ou 2160p

Cela ne signifie pas que les applications ne doivent pas se soucier de l'utilisation de la mémoire sur ces appareils, car certaines utilisations abusives spécifiques de la mémoire peuvent toujours épuiser la mémoire disponible et entraîner de mauvaises performances.

Cibles de mémoire sur les appareils TV à faible RAM

Lorsque vous mesurez la mémoire sur ces appareils, nous vous recommandons vivement de surveiller chaque section de la mémoire à l'aide du profileur de mémoire Android Studio. Les applications TV doivent profiler leur utilisation de la mémoire et s'efforcer de placer leurs catégories en dessous des seuils que nous définissons dans cette section.

Profileur de mémoire

Dans la section Comment la mémoire est-elle comptabilisée ?, vous trouverez une explication détaillée des chiffres de mémoire indiqués. Pour définir les seuils des applis TV, nous nous concentrerons sur trois catégories de mémoire :

  • Anonyme + Swap : mémoire composée de l'allocation Java + Native + Stack dans Android Studio.
  • Graphismes : directement signalés dans l'outil de profilage. Généralement composé de textures graphiques.
  • Fichier : signalé dans les catégories "Code" et "Autres" d'Android Studio.

À l'aide de ces définitions, le tableau suivant indique la valeur maximale que chaque type de groupe de mémoire doit utiliser :

Type de mémoire Purpose Cibles d'utilisation (1 Go)
Anonyme + Swap (Java + natif + pile) Utilisé pour les allocations, les tampons média, les variables et d'autres tâches nécessitant beaucoup de mémoire. < 160 Mo
Graphiques Utilisé par le GPU pour les textures et les tampons liés à l'affichage 30 à 40 Mo
Fichier Utilisé pour les pages de code et les fichiers en mémoire. 60 à 80 Mo

La mémoire totale maximale (mémoire anonyme + mémoire swap + mémoire graphique + mémoire de fichier) ne doit pas dépasser les valeurs suivantes :

  • 280 Mo de mémoire totale utilisée (Anon+Swap + Graphics + File) pour les appareils disposant de 1 Go de RAM.

Nous vous recommandons vivement de ne pas dépasser les limites suivantes :

  • 200 Mo d'utilisation de la mémoire sur (Anon+Swap + Graphics).

Mémoire de fichier

Voici quelques conseils généraux concernant la mémoire soutenue par des fichiers :

  • En général, la gestion de la mémoire des fichiers est bien gérée par la gestion de la mémoire de l'OS.
  • Pour le moment, nous n'avons pas constaté qu'il s'agissait d'une cause majeure de pression sur la mémoire.

Toutefois, lorsque vous utilisez la mémoire de fichier en général :

  • N'incluez pas les bibliothèques inutilisées dans votre compilation et utilisez de petits sous-ensembles de bibliothèques plutôt que les bibliothèques complètes lorsque cela est possible.
  • Ne gardez pas les fichiers volumineux ouverts en mémoire et libérez-les dès que vous avez terminé de les utiliser.
  • Pour minimiser la taille de votre code compilé pour les classes Java et Kotlin, consultez le guide Réduire, obscurcir et optimiser votre application.

Recommandations spécifiques pour les téléviseurs

Cette section fournit des recommandations spécifiques pour optimiser l'utilisation de la mémoire sur les appareils TV.

Mémoire graphique

Utilisez des formats et des résolutions d'image appropriés.

  • Ne chargez pas d'images dont la résolution est supérieure à celle de l'interface utilisateur de l'appareil. Par exemple, les images 1080p doivent être redimensionnées à 720p sur un appareil avec une interface utilisateur 720p.
  • Utilisez des bitmaps intégrés au matériel lorsque cela est possible.
    • Sur les bibliothèques comme Glide, activez la fonctionnalité Downsampler.ALLOW_HARDWARE_CONFIG, qui est désactivée par défaut. L'activation de cette option permet d'éviter la duplication des bitmaps qui seraient autrement présents à la fois dans la mémoire graphique et dans la mémoire anonyme.
  • Évitez les rendus intermédiaires et les rendus à nouveau
    • Vous pouvez les identifier avec Android GPU Inspector :
    • Dans la section "Textures", recherchez les images qui sont des étapes vers le rendu final plutôt que les éléments qui les composent uniquement. Il s'agit généralement d'un rendu intermédiaire.
    • Pour les applications du SDK Android, vous pouvez souvent les supprimer en utilisant l'indicateur de mise en page forceHasOverlappedRendering:false pour désactiver les rendus intermédiaires pour cette mise en page.
    • Consultez Éviter les rendus qui se chevauchent pour obtenir des informations utiles sur les rendus qui se chevauchent.
  • Évitez de charger des images d'espace réservé lorsque cela est possible. Utilisez @android:color/ ou @color pour les textures d'espace réservé.
  • Évitez de composer plusieurs images sur l'appareil lorsque la composition peut être effectuée hors connexion. Préférer charger des images autonomes plutôt que de composer des images à partir d'images téléchargées
  • Suivez le guide Gestion des bitmaps pour mieux gérer les bitmaps.

Mémoire Anon+Swap

Anon+Swap est composé d'allocations natives, Java et de pile dans le profileur de mémoire Android Studio. Utilisez ActivityManager.isLowMemoryDevice() pour vérifier si l'appareil est limité en mémoire et adaptez-vous à cette situation en suivant ces consignes.

  • Média :
    • Spécifiez une taille variable pour les tampons multimédias en fonction de la RAM de l'appareil et de la résolution de lecture vidéo. Cela devrait représenter une minute de lecture vidéo :
      1. 40 à 60 Mo pour 1 Go / 1080p
      2. 60 à 80 Mo pour 1,5 Go / 1080p
      3. 80 à 100 Mo pour 1,5 Go / 2160p
      4. 100 à 120 Mo pour 2 Go / 2160p
    • Libération de la mémoire média lors du changement d'épisode pour éviter l'augmentation de la quantité totale de mémoire anonyme.
    • Libérez et arrêtez immédiatement les ressources multimédias lorsque votre application est arrêtée : utilisez les rappels de cycle de vie d'activité pour gérer les ressources audio et vidéo. Si vous n'êtes pas une application audio, arrêtez la lecture lorsque onStop() se produit sur vos activités, enregistrez tout le travail que vous effectuez et définissez vos ressources comme devant être libérées. Pour planifier des tâches dont vous pourriez avoir besoin plus tard. Consultez la section Jobs et alarmes.
    • Faites attention à la mémoire tampon lors de la recherche dans la vidéo : les développeurs allouent souvent 15 à 60 secondes de contenu futur lors de la recherche pour que la vidéo soit prête pour l'utilisateur, mais cela crée une surcharge de mémoire supplémentaire. En général, ne prenez pas plus de 5 s de mémoire tampon future tant que l'utilisateur n'a pas sélectionné la nouvelle position de la vidéo. Si vous avez absolument besoin de précharger du temps supplémentaire lors de la recherche, assurez-vous de :
      • Allouez la mémoire tampon de recherche à l'avance et réutilisez-la.
      • La taille de la mémoire tampon ne doit pas dépasser 15 à 25 Mo (selon la mémoire de l'appareil).
  • Affectations :
    • Suivez les conseils concernant la mémoire graphique pour vous assurer de ne pas dupliquer d'images dans la mémoire anonyme.
      • Les images sont souvent les plus grandes consommatrices de mémoire. Leur duplication peut donc exercer une forte pression sur l'appareil. Cela est particulièrement vrai lors d'une navigation intensive dans les grilles d'images.
    • Libérez les allocations en supprimant leurs références lorsque vous déplacez des écrans : assurez-vous qu'il ne reste aucune référence à des bitmaps et à des objets.
  • Bibliothèques :
    • Profilez les allocations de mémoire des bibliothèques lorsque vous en ajoutez de nouvelles, car elles peuvent également charger des bibliothèques supplémentaires, ce qui peut également entraîner des allocations et créer des liaisons.
  • Mise en réseau :
    • N'effectuez pas d'appels réseau bloquants au démarrage de l'application. Ils ralentissent le temps de démarrage de l'application et créent une surcharge de mémoire supplémentaire au lancement, où la mémoire est particulièrement limitée par la charge de l'application. Affichez d'abord un écran de chargement ou de démarrage, puis effectuez les requêtes réseau une fois l'UI en place.

Liaisons

Les liaisons entraînent une surcharge mémoire supplémentaire, car elles chargent d'autres applications en mémoire ou augmentent la consommation de mémoire de l'application liée (si elle est déjà en mémoire) pour faciliter l'appel d'API. Par conséquent, cela réduit la mémoire disponible pour l'application au premier plan. Lorsque vous liez un service, faites attention au moment et à la durée d'utilisation de la liaison. Veillez à libérer la liaison dès qu'elle n'est plus nécessaire.

Liaisons types et bonnes pratiques :

  • API Play Integrity : utilisée pour vérifier l'intégrité de l'appareil
    • Vérifier l'intégrité de l'appareil après l'écran de chargement et avant la lecture du contenu multimédia
    • Libérez les références à Play Integrity StandardIntegrityManager avant de lire le contenu.
  • Bibliothèque Play Billing : utilisée pour gérer les abonnements et les achats avec Google Play
  • GMS FontsProvider
    • Sur les appareils à faible RAM, il est préférable d'utiliser des polices autonomes plutôt qu'un fournisseur de polices, car le téléchargement des polices est coûteux et FontsProvider associera des services pour le faire.
  • Bibliothèque Assistant Google : parfois utilisée pour la recherche et la recherche dans l'application. Si possible, remplacez cette bibliothèque.
    • Pour les applications leanback : utilisez la synthèse vocale Gboard ou la bibliothèque androidx.leanback.
    • Pour les applications Compose :
      • Utilisez la synthèse vocale Gboard pour implémenter la recherche vocale.
    • Implémentez Regarder ensuite pour rendre les contenus multimédias de votre application détectables.

Services de premier plan

Les services de premier plan sont un type spécial de service lié à une notification. Cette notification s'affiche dans la barre de notification des téléphones et des tablettes, mais les appareils TV n'ont pas de barre de notification au même sens que ces appareils. Même si les services de premier plan sont utiles, car ils peuvent continuer à s'exécuter lorsque l'application est en arrière-plan, les applications TV doivent respecter les consignes suivantes :

Sur Android TV et Google TV, les services de premier plan ne sont autorisés à continuer de s'exécuter une fois que l'utilisateur a quitté l'application que dans les cas suivants :

  • Pour les applications audio : Les services de premier plan sont uniquement autorisés à continuer de s'exécuter une fois que l'utilisateur a quitté l'application pour continuer à lire la piste audio. Le service doit être arrêté immédiatement après la fin de la lecture audio.
  • Pour toute autre application, tous les services de premier plan doivent être arrêtés une fois que l'utilisateur quitte votre application, car aucune notification ne l'informe que l'application est toujours en cours d'exécution et consomme des ressources.
  • Pour les jobs en arrière-plan, comme la mise à jour des recommandations ou des vidéos à regarder ensuite, utilisez WorkManager.

Tâches et alarmes

WorkManager est l'API Android de pointe pour planifier des tâches récurrentes en arrière-plan. WorkManager utilisera le nouveau JobScheduler lorsqu'il sera disponible (SDK 23 et versions ultérieures) et l'ancien AlarmManager lorsqu'il ne le sera pas. Pour suivre les bonnes pratiques concernant l'exécution de tâches planifiées sur un téléviseur, suivez les recommandations suivantes :

  • Évitez d'utiliser les API AlarmManager sur le SDK 23 et versions ultérieures, en particulier AlarmManager.set(), AlarmManager.setExact() et les méthodes similaires, car elles ne permettent pas au système de décider du moment opportun pour exécuter les tâches (par exemple, lorsque l'appareil est inactif).
  • Sur les appareils à faible RAM, évitez d'exécuter des jobs, sauf si cela est strictement nécessaire. Si nécessaire, utilisez WorkManager WorkRequest uniquement pour mettre à jour les recommandations après la lecture, et essayez de le faire pendant que l'application est encore ouverte.
  • Définissez Constraints WorkManager pour permettre au système d'exécuter vos tâches au moment opportun :

Kotlin

Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)
    .setRequiresStorageNotLow(true)
    .setRequiresDeviceIdle(true)
    .build()

Java

Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)
    .setRequiresStorageNotLow(true)
    .setRequiresDeviceIdle(true)
    .build()
  • Si vous devez exécuter des jobs régulièrement (par exemple, pour mettre à jour À suivre en fonction de l'activité de visionnage de contenu d'un utilisateur dans votre application sur un autre appareil), maintenez l'utilisation de la mémoire à un niveau bas en veillant à ce que la consommation de mémoire du job ne dépasse pas 30 Mo.

Considérations générales sur la mémoire

Les consignes suivantes fournissent des informations générales sur le développement d'applications Android :

  • Minimisez les allocations d'objets, optimisez la réutilisation des objets et libérez rapidement tous les objets inutilisés.
    • Ne conservez pas de références aux objets, en particulier aux bitmaps.
    • Évitez d'utiliser System.gc() et les appels de libération de mémoire directe, car ils interfèrent avec le processus de gestion de la mémoire du système. Par exemple, sur les appareils utilisant zRAM, un appel forcé à gc() peut augmenter temporairement l'utilisation de la mémoire en raison de la compression et de la décompression de la mémoire.
    • Utilisez LazyList, comme illustré dans un navigateur de catalogue dans Compose ou RecyclerView dans la boîte à outils Leanback UI désormais obsolète, pour réutiliser les vues et ne pas recréer les éléments de liste.
    • Mettez en cache localement les éléments lus à partir de fournisseurs de contenu externes qui sont peu susceptibles de changer et définissez des intervalles de mise à jour qui empêchent l'allocation de mémoire externe supplémentaire.
  • Recherchez d'éventuelles fuites de mémoire.
    • Faites attention aux cas typiques de fuite de mémoire, comme les références à l'intérieur de threads anonymes, la réallocation de tampons vidéo qui ne sont jamais libérés et d'autres situations similaires.
    • Utilisez l'empreinte de la mémoire pour déboguer les fuites de mémoire.
  • Générez des profils de référence pour minimiser la quantité de compilation à la demande nécessaire lors de l'exécution de votre application lors d'un démarrage à froid.

Comprendre la récupération directe de mémoire

Lorsqu'une application Android TV demande de la mémoire et que le système est sous pression, le noyau Linux, qui sous-tend Android, peut avoir à utiliser la récupération directe de mémoire.

Le processus consiste à mettre en veille tout thread d'allocation pour attendre que des pages de mémoire soient libérées. Cela se produit lorsque la récupération en arrière-plan n'est pas en mesure de maintenir un pool de mémoire suffisant de manière proactive.

Cela peut entraîner des pauses ou des à-coups visibles dans l'expérience utilisateur, car le système suspend l'allocation de threads jusqu'à ce qu'une mémoire suffisante soit disponible. En ce sens, les threads d'allocation ne sont pas limités aux appels de code d'application tels que malloc(). Par exemple, de la mémoire doit être allouée à la page dans les pages de code.

Récapitulatif des outils