La gestion de la mémoire bitmap est essentielle pour limiter les risques de mémoire insuffisante (OOM, Out-Of-Memory) dans les applications Android riches en visuels. Les développeurs savent qu'ils doivent réduire la taille des images, mais pour assurer la stabilité à long terme de l'application, ils doivent maîtriser deux autres piliers essentiels : la réutilisation et le recyclage. Cette session propose un guide pratique pour optimiser les performances de la mémoire à l'aide d'outils modernes, de bibliothèques Open Source comme Glide et Coil, et d'API au niveau du système.
Dans cette session, vous apprendrez à diagnostiquer les régressions de mémoire réelles à l'aide d'Android Vitals, à implémenter des modèles de mise en cache avancés (réutilisation) avec Glide et Coil, et à libérer de la mémoire de manière proactive (recyclage) à l'aide du rappel onTrimMemory.
BitmapRegionDecoder plutôt que de charger des images en résolution maximale surdimensionnées.onTrimMemory. Arrêtez l'interrogation et les animations en arrière-plan dans onStop pour éviter l'augmentation de la mémoire en arrière-plan.Transcription
Fung Lam
Oui, il reste environ une heure avant le déjeuner, alors restez avec moi.
Je m'appelle Fung Lam et je suis ingénieur DevRel pour Android à Londres.
Oui, merci.
Avant de commencer, puis-je vous poser une question ?
Combien d'entre vous ont acheté un nouveau téléphone cette année ?
Waouh !
Waouh !
Sois honnête, je ne t'en voudrai pas si tu n'achètes pas, Pixel.
Eh bien, le nouveau téléphone est plus cher, n'est-ce pas ?
Oui, c'est pour cela que certains d'entre vous n'ont pas acheté de nouveau téléphone.
Pourquoi est-ce plus cher ?
Le savez-vous ?
Non.
RAM.
RAM, oui, oui.
Oui, la RAM est un vrai défi pour Android et pour nous, les développeurs.
C'est pourquoi je suis ici aujourd'hui pour vous expliquer comment nous pouvons surmonter le problème de la mémoire, de la RAM.
Lors de Google I/O et de Google I/O Connect, nous avons organisé des sessions sur la mémoire, de l'allocation de la mémoire par le système d'exploitation au profilage de votre application, en passant par l'optimisation de votre code à l'aide de R8. J'ai également abordé brièvement le sujet des bitmaps.
Vous avez pu voir mes photos dans la capture d'écran.
L'optimisation de la mémoire est un vaste domaine qui englobe différents aspects, tels que l'environnement d'exécution Android, le garbage collection, les outils de performance, l'allocation de mémoire des objets, le compilateur, etc. Dans cette session de 30 minutes, je vais vous expliquer comment optimiser la mémoire des bitmaps.
Vous vous demandez peut-être pourquoi utiliser Bitmap.
Pourquoi s'intéresser à la mémoire bitmap ?
Parce qu'une image vaut mieux qu'un long discours.
C'est vrai, et je vais vous expliquer pourquoi.
Sur l'écran d'un téléphone, par exemple un Pixel 7, un Pixel 9 ou un Pixel 11 Pro, la résolution d'écran est d'environ 2 500 x 1 200 ou 1 400.
Ainsi, pour chaque pixel, nous avons 32 bits de profondeur de couleur.
Pour l'image en plein écran, je n'ai pas assez de doigts, mais elle occupera environ 15 Mo de mémoire, 50 Mo.
Dans un OS, un mot fait 64 bits, soit 8 octets.
Un millier de mots ne représente que 8 Mo.
Oui, 8K.
Une image vaut donc vraiment mieux qu'un long discours.
Oui, c'est important.
Une image fait déjà 15 Mo.
Que se passe-t-il si votre application comporte 10 ou 100 images ?
Eh bien, c'est un nombre élevé.
En fait, Bitmap est généralement le plus gros consommateur de mémoire de votre application.
Comme je l'ai mentionné, lorsque l'image est décodée en mémoire, elle est développée en fonction de ses dimensions et de sa profondeur de couleur.
Il ne s'agit pas de savoir si la taille du fichier est au format JPEG ou PNG.
Non, il sera décodé et utilisera de la mémoire.
Ainsi, quelques images en pleine résolution peuvent facilement occuper des centaines de mégaoctets de la mémoire de votre appareil.
Deuxièmement, lorsque la mémoire est très limitée, le système d'exploitation doit consommer plus d'énergie pour gérer la consommation de mémoire.
Cela peut donner l'impression que l'UI est saccadée.
L'OS peut également arrêter votre application pour libérer de la mémoire.
Sinon, votre application rencontrera également une erreur ou une exception de mémoire insuffisante.
Heureusement, cette phase est généralement une grande réussite avec peu d'efforts.
La plupart des pics de mémoire sont dus à des erreurs courantes, qui pourraient être évitées, et nous pourrions les corriger sans compromettre la qualité de l'image.
Vous savez maintenant que la mémoire bitmap est importante pour votre application, non seulement pour la rendre fluide, mais aussi pour la stabilité et la rétention des utilisateurs.
Aujourd'hui, nous allons donc passer en revue cinq étapes simples pour assurer le bon fonctionnement de votre application.
Nous allons d'abord nous pencher sur la qualité technique de Google Play, la nouvelle exigence de qualité technique, qui inclut la limite de mémoire à venir et où vous pouvez facilement la vérifier.
Ensuite, je partagerai une étude de cas sur 500 applications Android, leur utilisation de la mémoire, si elles le font correctement ou si nous pouvons en tirer des enseignements.
Ensuite, je vous montrerai comment optimiser l'utilisation de la mémoire des bitmaps.
Enfin, nous vous présenterons des outils qui pourraient vous aider à vérifier votre propre application.
Fung Lam
Tout d'abord, la nouvelle exigence de qualité de Google Play.
À quelle fréquence utilisez-vous la Google Play Console ?
Tous les mois ?
Toutes les semaines ?
Jamais ?
Ces rires signifient oui, n'est-ce pas ?
Non ?
Non.
Avez-vous consulté la section Android Vitals ?
Oui, oui.
Oui, donc vous savez quelque chose avant que je ne fasse ça.
C'est pourquoi Google Play a mis en place de nouvelles exigences de qualité concernant le seuil de performances.
Android et Play souhaitent que les utilisateurs profitent d'applications de qualité.
Ce seuil est donc également un élément que nous essayons d'optimiser pour l'utilisateur final.
En d'autres termes, lorsque votre application s'exécute en arrière-plan ou au premier plan, le 90e centile de l'utilisation de la mémoire ne doit pas dépasser ce seuil.
Sinon, la visibilité de votre application sur le Play Store pourrait être réduite, ou quelque chose comme ça."
Ces mesures seront appliquées à partir de février 2027 pour les applications et les jeux.
Le dépassement de ce seuil a un impact direct sur l'évaluation de l'état de votre application.
Assurez-vous donc d'accéder à la Google Play Console et de vérifier votre 90e centile.
et d'apporter les améliorations nécessaires avant la date limite.
Il s'agit de l'animation.
"Si vous accédez à la Google Play Console, vous verrez Android Vitals.
Il y a une partie de la mémoire.
Vous pouvez voir le 90e percentile.
Si vous cliquez dessus, vous pouvez voir l'évolution de la consommation moyenne de mémoire au fil du temps.
Il existe également différents niveaux par RAM, par facteur de forme, par densité d'écran et par version d'OS, qui vous aident à isoler les goulots d'étranglement de la mémoire sur des appareils d'entrée de gamme spécifiques ou sur des versions d'application spécifiques.
Vous vous dites peut-être : "Merci de m'avoir dit que mon application dépassait le seuil.
Mais en réalité, je ne sais pas ce qui ne va pas.
Pouvez-vous m'aider ?"
Oui, c'est pourquoi je suis là pour vous aider.
Je vais essayer de vous faciliter la tâche. Imaginez que vous regardez la télévision et que vous n'avez plus d'image ou de son. Que faites-vous ?
Vous sortez le manuel de votre téléviseur, vous allez aux toutes dernières pages, qui sont généralement le guide de dépannage, et vous lisez : "Si votre téléviseur n'émet aucun son, essayez d'appuyer sur ce bouton ou sur celui-là."
Oui, de même, aujourd'hui, j'ai identifié un problème courant.
Nous espérons que ce guide de dépannage vous sera utile.
Normalement, quand on lit le manuel d'un téléviseur, on commence par la première page, qui est généralement consacrée aux outils de diagnostic pour vous aider à comprendre comment fonctionne le téléviseur.
Mais maintenant que je sais que ces sujets sont moins intéressants, permettez-moi de commencer par le dépannage, et nous y reviendrons plus tard.
Et pour être honnête, c'est vraiment comme ça que j'ai lu le manuel du téléviseur.
Pour découvrir le problème de mémoire bitmap le plus fréquent lié à l'allocation de bitmaps dans l'écosystème Android, j'ai examiné environ 500 applications populaires sur Android.
Je lance l'application, je me connecte, je consulte différentes pages, je vais plus loin, puis je reviens en arrière.
Je teste aussi l'application sur différents appareils avec différentes RAM, comme un appareil Android Go avec 2 Go de mémoire, un appareil d'entrée de gamme avec 4 Go, un appareil de milieu de gamme avec 4, 6 ou 8 Go, ou un appareil pliable haut de gamme avec un très grand écran, une très bonne qualité d'écran et 16 Go de mémoire.
Nous avons mesuré l'utilisation de la mémoire sur cet appareil dans différents états de l'application, en capturant l'empreinte de la mémoire et en utilisant différents outils, y compris la compétence d'IA Perfetto, pour analyser l'allocation et identifier les anti-modèles courants, c'est-à-dire les erreurs courantes que nous commettons à grande échelle.
Voilà comment la mémoire a évolué dans les différents états de l'activité.
Grâce à cette analyse, nous identifions plus de 15 problèmes courants, allant des images surdimensionnées à l'augmentation des caches en arrière-plan.
C'est l'étude de cas principale que nous allons partager et explorer aujourd'hui.
Fung Lam
Oui, l'étude de cas que je partage maintenant porte sur la recherche sur 500 applications.
Puisque vous êtes là, je pense que nous aimons tous Android, n'est-ce pas ?
Nous aimons tous Android.
Imaginons que l'un d'entre nous adore Android, mais qu'il ne soit pas un expert en développement Android.
Il crée donc une application Android pour commander des figurines Android.
Mais il y a quelques erreurs.
Voyons si c'est la première erreur.
"Hey, sur la page produit, nous avons une mise en page sur deux colonnes montrant différentes figurines Android.
Pour chaque colonne, l'image mesure environ 400 x 400 pixels.
Cependant, l'application télécharge en réalité une seule image depuis le serveur, quelle que soit la taille de mon écran.
Il télécharge une image de 3 000 x 3 000.
Oui, parce que les développeurs se disent : "Android est tellement beau, tellement incroyable.
Je veux montrer le meilleur à l'utilisateur."
Mais en fait, non, car elle est limitée par la dimension de la vue d'image.
Ainsi, même si vous téléchargez l'image de grande taille, elle ne s'affiche pas de manière optimale pour l'utilisateur.
De plus, cette grande image a entraîné une utilisation supplémentaire de la mémoire.
Et même si nous le sous-échantillonnons, le processus de sous-échantillonnage utilise également de la mémoire supplémentaire.
Comment pouvons-nous aider ce développeur à résoudre le problème ?
Si vous connaissez les CDN, vous savez qu'ils peuvent gérer certaines opérations sur les images, comme le recadrage, le redimensionnement, la rotation, l'application de filtres ou la transformation d'une image carrée en image ronde.
Par requête HTTP, le CDN peut gérer quelque chose.
Ainsi, même si votre serveur côté serveur n'héberge qu'une seule image, vous pouvez utiliser un CDN pour envoyer une image redimensionnée à votre appareil.
Donc, premièrement, lorsque vous demandez l'image, essayez d'utiliser un CDN pour demander l'image à la taille de l'ImageView.
Mais si ce n'est pas possible, par exemple si l'image est parfois un peu plus grande que l'ImageView, n'oubliez pas de la sous-échantillonner pour la redimensionner dans le code.
Ainsi, lorsqu'elle est chargée en mémoire, elle charge une image plus petite, et non l'image en résolution maximale.
Voici un exemple de code pour Coil.
// 1. Ask CDN for the exact UI card dimensions (e.g. 540x675)
val sizedUrl = "$cdnBaseUrl?w=$cardWidthPx&h=$cardHeightPx&fmt=webp"
// 2. Enforce exact downsampling + Hardware Bitmap config
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(sizedUrl)
.size(cardWidthPx, cardHeightPx) // Never use Size.ORIGINAL!
.precision(Precision.EXACT) // Downsample to exact card size
.bitmapConfig(Bitmap.Config.HARDWARE)
.build(),
contentDescription = product.title
)Vous pouvez transmettre la taille comme indiqué en jaune.
Oui, vous pouvez transmettre la taille en tant que requête HTTP.
Ensuite, dans le code en surbrillance, vous pouvez voir qu'il ne faut pas utiliser Size.ORIGINAL.
Oui, utilisez la largeur et la hauteur, qui seront transmises en tant que paramètres.
Utilisez la précision pour sous-échantillonner l'image à la taille exacte.
Si possible, utilisez Bitmap.Config.HARDWARE, qui utilise également moins de mémoire, car l'image sera stockée directement dans la VRAM.
De même, dans cette application, une nouvelle version d'objets de collection Android est disponible. Il y a donc une longue image que l'utilisateur peut balayer vers la gauche et la droite pour voir les différents designs de la nouvelle figurine Android.
Mais il y a un autre problème.
Pour l'image longue, une grande partie se trouve en dehors de l'écran, ce qui signifie que l'utilisateur ne peut pas la voir, mais qu'elle est chargée en mémoire.
Pouvons-nous faire mieux ?
Oui.
Nous pourrions la découper en une image de plus en plus petite et n'afficher que l'image affichée à l'écran.
Ceux qui se trouvent au-delà de l'écran ne seront pas chargés ou seront déchargés.
Il utilisera donc moins de mémoire.
Dans ce cas, environ 60 % de mémoire a été économisée.
Bien sûr, si vous recadrez l'image dans un petit rectangle, vous économiserez davantage.
De même, si un utilisateur souhaite proposer une fonctionnalité permettant de zoomer pour voir chaque détail de la figurine Android, une erreur courante consiste à charger une image très volumineuse pour pouvoir zoomer encore et encore.
Mais non, vous n'avez pas à le faire.
Une meilleure approche consiste à laisser l'utilisateur zoomer sur l'image basse résolution d'origine déjà chargée, puis, lorsqu'il arrête de pincer, à calculer les coordonnées et à charger uniquement la zone calculée de l'image haute résolution.
Seule cette zone visible sera donc chargée en mémoire.
Comment faire ?
Il existe une API appelée BitmapRegionDecoder, qui vous aide à charger une région spécifique d'une image.
Bien sûr, certains outils et bibliothèques peuvent également vous aider.
Vous pouvez le rechercher sur GitHub.
Certaines applications de bandes dessinées ou de cartes l'utilisent déjà.
Vous pouvez les utiliser comme référence.
De plus, nous sommes humains et nous faisons des erreurs.
L'IA peut aussi faire des erreurs.
Pour éviter les erreurs, nous pouvons également ajouter une limite lors du chargement de l'image.
Par exemple, dans ce cas, je pourrais dire : "Utilisons la taille de la fenêtre ou la dimension de l'écran comme limite et plaçons-la dans une fonction min, min de la largeur de la requête ou de la largeur de la limite."
// Query active window bounds instead of hardcoding 1080 / 1920
val bounds = WindowMetricsCalculator.getOrCreate()
.computeCurrentWindowMetrics(activity).bounds
// Clamp target dimensions so Bitmap NEVER exceeds the active window
val safeWidth = minOf(requestedWidthPx, bounds.width())
val safeHeight = minOf(requestedHeightPx, bounds.height())
val bitmap = Bitmap.createBitmap(safeWidth, safeHeight, config)Ainsi, avec cette fonction, chaque fois que je charge une image, elle ne dépassera jamais les limites.
Ainsi, il ne chargera pas d'image plus grande que la résolution de l'écran.
N'oubliez pas de ne décoder que la région visible de l'image, ce qui vous permettra d'économiser de la mémoire.
Fung Lam
Et encore une fois, dites-vous que cette application ne provient pas d'un seul développeur.
En fait, il s'agit de plusieurs contributeurs qui peuvent utiliser des outils différents.
Par exemple, une partie de l'entraînement consiste à utiliser… Oh, désolé.
Je vais recommencer.
Idem pour cette application.
Bonjour, sur la page produit, j'ai une liste de produits et j'ai vu cette image, je l'ajoute au panier.
Quelques minutes plus tard, je passe à la caisse et, oh, la même image réapparaît.
Mais s'agit-il vraiment de la même image ou de deux objets différents en mémoire ?
Oui, l'une des erreurs courantes que nous avons constatées est que l'application charge beaucoup d'images.
Cependant, ils ont également beaucoup d'images en double dans la mémoire.
Si vous utilisez un chargeur d'images, vous pouvez effectivement transmettre une clé pour la mise en cache.
Ainsi, lorsque la même image est utilisée, vous pouvez utiliser l'objet Bitmap déjà alloué au lieu d'en créer un.
Dans Android Studio, vous pouvez utiliser le Profileur de mémoire pour savoir si votre application comporte des bitmaps en double.
J'en ai parlé lors de ma session Google I/O. Vous pouvez donc retourner sur YouTube pour regarder la vidéo.
Si vous n'utilisez pas Android Studio, vous pouvez également utiliser la commande adb pour capturer l'empreinte de la mémoire, puis utiliser le langage SQL du graphique de tas Perfetto pour vérifier si votre application comporte des bitmaps en double.
Voici un exemple de code dans Coil pour ajouter une clé pour la mise en cache des bitmaps.
// Strip volatile query tokens (?from=list / ?sig=...) for cache key
val canonicalKey = MemoryCache.Key(productUrl.substringBefore("?"))
// On Checkout Screen: reuse the 200x200 Bitmap already loaded in the List!
AsyncImage(
model = ImageRequest.Builder(context)
.data(productUrl)
.placeholderMemoryCacheKey(canonicalKey) // Instant zero-decode reuse!
.memoryCacheKey(canonicalKey)
.diskCacheKey(productUrl.substringBefore("?"))
.build(),
contentDescription = product.title
)Et maintenant, oui.
Revenons à l'exemple précédent.
Bonjour, mon application a un autre développeur.
Certaines parties utilisent des vues, donc Glide est utilisé comme chargeur d'images.
Certains d'entre eux utilisent Coil, car ils utilisent Compose.
Vous venez de mentionner que le chargeur d'images nous aide à effectuer la mise en cache. Est-ce un problème si j'ai plusieurs chargeurs d'images ?
Il y a forcément un problème.
Par défaut, le chargeur d'images utilise X % de la taille de la mémoire du tas de mémoire comme taille de cache par défaut.
Si vous avez un chargeur d'images, il utilise X % de votre mémoire.
Si vous avez deux chargeurs d'images, la mémoire sera multipliée par deux.
Si vous avez trois chargeurs d'images, le coût sera multiplié par trois.
Et si votre chargeur d'images ne partage pas le même pool, il se passera la même chose.
Comme sur la page produit, je charge une image à l'aide de Glide, puis sur la page de paiement, la même image sera chargée à l'aide de Coil.
Toutefois, deux images seront toujours stockées dans la mémoire.
Ce n'est pas bon.
Oui, pour résoudre ce problème, l'idéal est d'utiliser un seul chargeur d'images ou un seul pool partagé pour la mise en cache.
Mais que faire si cela représente beaucoup de travail d'ingénierie et que je ne peux pas le faire ?
Une solution plus simple consiste à mesurer le pourcentage d'images qui sera géré par ce chargeur d'images, le pourcentage d'images qui sera géré par un autre chargeur d'images et à définir une pondération pour les différents chargeurs d'images, comme dans ce code.
val totalBudgetBytes = computeAdaptiveCacheBytes(context)
val numberOfLoaders = 2 // e.g., Team A (Glide) + Team B (Coil)
// Approach A (With Stats): Weight by image traffic (70% Team A / 30% Team B)
val loaderABudget = (totalBudgetBytes * 0.70).toInt()
val loaderBBudget = (totalBudgetBytes * 0.30).toInt()
// Approach B (Simple Fix without stats): Divide by number of loaders!
val simpleSplitBudget = totalBudgetBytes / numberOfLoaders
MemoryCache.Builder()
.maxSizeBytes(simpleSplitBudget)
.build()Je pourrais définir loaderABudget sur 70 % et loaderBBudget sur 30 %. C'est une autre possibilité.
Si vous ne savez vraiment pas quelle quantité d'images sera traitée par chaque chargeur, il est préférable de simplement diviser.
"Si j'ai deux chargeurs d'images, je divise par 2. Si j'en ai trois, je divise par 3 au lieu d'utiliser la valeur par défaut, car cela entraînerait une utilisation excessive de la mémoire."
Imaginons que, sur la page produit, nous nous attendions à ce que l'utilisateur fasse défiler l'écran pour voir plus de produits.
En général, nous mettons en cache environ 50 images ou 50 Mo d'images.
Mais que se passe-t-il si l'application passe en arrière-plan ?
Si l'application est en arrière-plan, l'utilisateur ne navigue peut-être pas.
Si l'utilisateur ne navigue pas, dois-je quand même conserver cette quantité d'images en cache dans la mémoire ?
Non, nous devrions le publier.
Oui, nous devons respecter le rappel onTrimMemory.
Oui, de nombreuses applications n'implémentent pas ou ne respectent pas le rappel onTrimMemory.
Pour le rappel onTrimMemory, nous devons prendre en compte deux signaux.
Le premier est UI_HIDDEN, qui correspond à l'action d'appuyer sur le bouton d'accueil. Il ne se déclenche pas immédiatement après que l'utilisateur a appuyé sur le bouton d'accueil, mais un certain temps après, ce qui signifie que l'application est dans un état où aucune UI n'est visible par l'utilisateur.
On pourrait se dire : "L'utilisateur n'utilise pas votre application.
Vous devez libérer les ressources pour l'OS."
Nous devons donc réduire la taille du cache.
L'autre est TRIM_MEMORY_BACKGROUND, ce qui signifie que l'application est déjà en arrière-plan depuis longtemps et que l'OS a vraiment besoin de ressources.
À ce stade, vous devez libérer autant de mémoire que possible, presque 100 %.
Cela réduit également le risque que votre application soit arrêtée par l'OS.
En effet, si un processus demande de la mémoire, vous lui en donnez, et l'OS gère le reste.
Si ce n'est pas le cas, l'OS n'a pas le choix : il doit agir.
Fung Lam
Une autre erreur majeure que je vois souvent, c'est que l'application ne consomme pas moins de mémoire lorsqu'elle passe en arrière-plan.
Cependant, j'ai constaté que des applications utilisaient plus de mémoire en arrière-plan.
Pourquoi ?
C'est pour ça que je fais des recherches.
J'ai essayé de comprendre pourquoi.
L'une des raisons est que leur application comporte une animation ou un carrousel.
Par exemple, elles téléchargent une nouvelle image toutes les 15 secondes et l'affichent à l'utilisateur."
Cependant, le développeur n'a pas arrêté l'animation lorsque l'application passe en arrière-plan.
Ainsi, même si l'application est en arrière-plan, le code continue de s'exécuter, de récupérer une nouvelle image, de la décoder et d'utiliser plus de mémoire.
C'est donc quelque chose que l'application ne devrait pas faire.
Oui.
Nous devons arrêter toutes les animations, la mise en cache ou les tâches non liées lorsque l'application passe en arrière-plan.
Si vous connaissez le cycle de vie d'une activité, vous savez qu'il existe des événements onPause et onStop. Assurez-vous d'implémenter onStop, onPause et onDestroyView pour arrêter l'animation, les demandes d'extraction d'images ou d'autres éléments liés à l'activité qui ne nécessitent pas l'attention de l'utilisateur.
override fun onStop() {
super.onStop()
// 1. Stop animations so they don't decode next frames in background!
bannerAnimationView.cancelAnimation()
animatedImageDrawable?.stop()
// 2. Cancel next-image buffering / prefetch queues!
nextImagesBufferingJob?.cancel()
Glide.with(this).pauseRequests()
}
override fun onDestroyView() {
super.onDestroyView()
bannerAnimationView.clearAnimation() // Release resources!
}Fung Lam
Je sais que j'ai déjà évoqué de nombreux cas avec vous, mais j'ai dit : "J'ai plus de 15 cas."
Je sais que si je vous parle de tous les cas, vous risquez de tout oublier.
Quand vous rentrerez chez vous, vous ferez une recherche sur Google : "Hey, comment résoudre mes problèmes de mémoire ?"
Je vous aide donc à vous en souvenir facilement.
Oui.
Je vais l'associer à un framework 3R très connu pour vous aider à les mémoriser.
Je pense que tout le monde devrait connaître ce framework des 3 R.
La première est la réduction, c'est-à-dire comment réduire la mémoire lors du chargement d'une image.
Bien sûr, comme je l'ai mentionné, nous pourrions sous-échantillonner l'image pour obtenir la bonne dimension lorsque nous la récupérons du serveur.
Nous optimisons la profondeur de couleur ou utilisons un bitmap matériel si possible.
Si vous n'utilisez pas de CDN, essayez d'en utiliser un.
Ainsi, lorsque nous récupérons l'image depuis le serveur, nous obtenons simplement l'image qui correspond le mieux à la taille.
Réutilisation : nous utilisons le cache pour que l'image chargée en mémoire soit réutilisée et que nous n'ayons pas d'image bitmap en double dans notre mémoire.
Et bien sûr, essayez de ne pas avoir plusieurs chargeurs d'images dans votre code, car cela est moins efficace.
Réduire, réutiliser et… quel est le troisième ?
Le savez-vous ?
Recycler.
Réessayer ?
Recycler, oui, recycler.
Oui, nous avons appris à réduire, réutiliser et recycler à l'école primaire, même si nous n'y avons pas appris à coder.
Oui, recycler, oui.
Oui, il s'agit de libérer de la mémoire pour que d'autres applications ou l'OS puissent l'utiliser.
Il y a aussi des choses comme le recyclage.
Tout d'abord, j'ai mentionné l'événement onTrimMemory. Il existe deux signaux que l'OS vous envoie lorsque votre UI est masquée ou que l'OS a vraiment besoin de ressources.
Oui, essayez de traiter cette demande.
Et bien sûr, si vous avez une animation, un chargeur d'images ou une autre tâche en cours d'exécution au premier plan, veillez à l'arrêter lorsque l'événement onStop se produit.
Alors, réduisez, réutilisez, recyclez.
Facile à retenir, n'est-ce pas ?
Fung Lam
Revenons maintenant à la partie moins intéressante du manuel du téléviseur.
Comment utiliser différents kits d'outils pour détecter et mesurer efficacement ce problème de mémoire ?
La première est la Google Play Console, Android Vitals et l'API Reporting.
Oui, vérifiez le 90e centile dans la Google Play Console.
Où vous trouvez-vous en ce moment ?
Vous pouvez également utiliser l'API Reporting si vous ne consultez pas la page Web.
Vous pouvez toujours utiliser l'API pour obtenir la valeur.
Oui, assurez-vous que les seuils sont de 200 Mo et 400 Mo.
Assurez-vous de ne pas dépasser le seuil.
Il existe également des commandes ADB pour vous aider, par exemple pour capturer l'empreinte de la mémoire ou pour envoyer un événement afin de simuler que votre application est maintenant à l'étape UI_HIDDEN.
adb shell dumpsys meminfo <pkg> # watch Native Heap!
adb shell am send-trim-memory <pkg> UI_HIDDENSi vous utilisez Android Studio, c'est parfait.
Pourtant, c'est toujours un bon ami pour vous.
Vous pouvez utiliser le profileur Android Studio pour capturer l'empreinte de la mémoire, afin de disposer d'une UI permettant de voir l'utilisation réelle de la mémoire et l'allocation de mémoire.
Et bien sûr, nous parlons d'IA avec Gemini dans Android Studio ou d'autres compétences Android disponibles.
Il vous aide à analyser l'empreinte de la mémoire et à utiliser les traces SQL Perfetto pour trouver le bitmap, afin d'identifier le propriétaire du cache.
Mon image est-elle chargée par Glide ou par Coil ?
Y a-t-il d'autres fuites de mémoire dans mon application ?
Certains d'entre vous se disent peut-être : "Je n'utilise plus l'IDE.
Je suis ingénieur de requêtes.
J'envoie simplement un prompt à l'agent IA pour qu'il écrive le code."
Oui, je sais.
Je fais aussi du vibe coding pendant mon temps libre.
Je vous montre cela à titre d'exemple.
Oui, ce n'est qu'une partie du prompt que j'utilise pour mes recherches sur les 500 applications.
Oui, juste une partie.
Vous savez que l'écran n'est pas grand.
Si j'en mettais plus, vous ne pourriez presque pas le voir.
Mais cela vous donne une idée de la façon dont vous pouvez utiliser l'IA pour automatiser, capturer l'utilisation de la mémoire, y compris l'empreinte de la mémoire, et analyser et vous fournir des résultats.
Et oui, je pourrais ajouter une phrase ici : "Fournis-moi également la correction de code pour mon code."
J'espère que cela vous rassurera. La mémoire est un vaste domaine.
Je n'ai pas étudié les systèmes d'exploitation ni l'architecture des systèmes informatiques dans le cadre de mes études d'informatique à l'université.
Mais en fait, ce n'est pas si effrayant, ce n'est pas si difficile."
Oui, vous pouvez facilement analyser votre application.
Veuillez l'essayer.
Mais bien sûr, l'IA fera des erreurs. Il est donc toujours important de connaître les principes fondamentaux de l'utilisation de la mémoire et de la façon dont l'OS l'alloue.
C'est pourquoi je vous invite à assister à ma conférence et à regarder notre vidéo sur YouTube.
Fung Lam
Récapitulons.
Tout d'abord, réduire.
Assurez-vous que l'image est à la bonne taille.
Vous divisez l'image en images plus petites pour ne pas tout charger en mémoire, mais seulement l'image visible par l'utilisateur.
Utilisez le matériel bitmapConfig ou RGB_565 dans la mesure du possible.
Réutilisez les bitmaps. Assurez-vous d'avoir une stratégie de mise en cache et utilisez un chargeur d'images pour réduire le nombre de bitmaps en double dans la mémoire.
Et bien sûr, n'utilisez pas plusieurs images bitmap, même si vous essayez de remplacer la taille de cache par défaut de ces chargeurs d'images.
Et nous ne réutilisons pas, nous ne recyclons pas.
Oui.
La gestion de la mémoire de votre application doit s'adapter à l'état de l'application.
Si l'UI est masquée, vous devriez utiliser moins de mémoire.
Si l'OS vous demande vraiment de libérer de la mémoire, libérez-en autant que possible.
et d'arrêter de consommer les ressources, qu'il s'agisse de bitmaps, d'énergie ou de ressources de calcul.
Oui, arrêtez le minuteur de la bannière, arrêtez l'animation, arrêtez la mise en commun des images.
Enfin, assurez-vous de surveiller les performances.
Accédez régulièrement à la Google Play Console.
Assurez-vous qu'il s'agit d'un entraînement.
Cela vous aidera à comprendre l'état actuel du 90e centile de l'utilisation de la mémoire de votre application pour l'ensemble de la population d'utilisateurs.
Cela vous aidera également à identifier les problèmes rapidement.
Oui.
Pour effectuer un profilage localement, vous pouvez utiliser le profileur de mémoire Android Studio.
Nous pouvons également utiliser des commandes ADB.
Nous avons également des compétences Android.
Mais si vous souhaitez consulter les statistiques d'un utilisateur réel, la Google Play Console est l'outil qu'il vous faut.
Vous pouvez également utiliser l'API Google Play Reporting pour obtenir ces données sans avoir à vous connecter à la Play Console.
Comme toujours, si vous souhaitez en savoir plus, consultez developer.android.com/memory.
Réduisez, réutilisez et recyclez avec ce produit.
Nous pourrions créer un meilleur… J'espère que nous pourrons créer ensemble un meilleur écosystème Android pour surmonter les défis auxquels nous sommes confrontés.
Merci et bon codage !
Je tiendrai bientôt une permanence à l'extérieur. Si vous avez des questions, n'hésitez pas à venir me voir.
Le prochain intervenant va bientôt monter sur scène. Veuillez patienter. Merci beaucoup.
// 1. Ask CDN for the exact UI card dimensions (e.g. 540x675)
val sizedUrl = "$cdnBaseUrl?w=$cardWidthPx&h=$cardHeightPx&fmt=webp"
// 2. Enforce exact downsampling + Hardware Bitmap config
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(sizedUrl)
.size(cardWidthPx, cardHeightPx) // Never use Size.ORIGINAL!
.precision(Precision.EXACT) // Downsample to exact card size
.bitmapConfig(Bitmap.Config.HARDWARE)
.build(),
contentDescription = product.title
)// Query active window bounds instead of hardcoding 1080 / 1920
val bounds = WindowMetricsCalculator.getOrCreate()
.computeCurrentWindowMetrics(activity).bounds
// Clamp target dimensions so Bitmap NEVER exceeds the active window
val safeWidth = minOf(requestedWidthPx, bounds.width())
val safeHeight = minOf(requestedHeightPx, bounds.height())
val bitmap = Bitmap.createBitmap(safeWidth, safeHeight, config)// Strip volatile query tokens (?from=list / ?sig=...) for cache key
val canonicalKey = MemoryCache.Key(productUrl.substringBefore("?"))
// On Checkout Screen: reuse the 200x200 Bitmap already loaded in the List!
AsyncImage(
model = ImageRequest.Builder(context)
.data(productUrl)
.placeholderMemoryCacheKey(canonicalKey) // Instant zero-decode reuse!
.memoryCacheKey(canonicalKey)
.diskCacheKey(productUrl.substringBefore("?"))
.build(),
contentDescription = product.title
)val totalBudgetBytes = computeAdaptiveCacheBytes(context)
val numberOfLoaders = 2 // e.g., Team A (Glide) + Team B (Coil)
// Approach A (With Stats): Weight by image traffic (70% Team A / 30% Team B)
val loaderABudget = (totalBudgetBytes * 0.70).toInt()
val loaderBBudget = (totalBudgetBytes * 0.30).toInt()
// Approach B (Simple Fix without stats): Divide by number of loaders!
val simpleSplitBudget = totalBudgetBytes / numberOfLoaders
MemoryCache.Builder()
.maxSizeBytes(simpleSplitBudget)
.build()override fun onStop() {
super.onStop()
// 1. Stop animations so they don't decode next frames in background!
bannerAnimationView.cancelAnimation()
animatedImageDrawable?.stop()
// 2. Cancel next-image buffering / prefetch queues!
nextImagesBufferingJob?.cancel()
Glide.with(this).pauseRequests()
}
override fun onDestroyView() {
super.onDestroyView()
bannerAnimationView.clearAnimation() // Release resources!
}adb shell dumpsys meminfo <pkg> # watch Native Heap!
adb shell am send-trim-memory <pkg> UI_HIDDEN