Fuites mémoire et corrections
Trouvez et éliminez les fuites.
Fuites mémoire et corrections est une leçon Android Academy gratuite sur CoddyKit. Ceci est la leçon 3 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Android Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Android Academy comprend 4 leçons au total.
Qu'est-ce qu'une fuite mémoire ?
Une fuite mémoire se produit lorsque des objets qui ne sont plus nécessaires ne peuvent pas être récupérés par le ramasse-miettes, parce que quelque chose conserve encore une référence vers eux. Sous Android, la victime classique est une Activity ou un Context qui persiste après la destruction de son écran.
Les fuites font augmenter votre tas au fil du temps, déclenchent des collectes plus fréquentes (ce qui met l'interface en pause et provoque des saccades) et finissent par faire planter l'application avec une OutOfMemoryError. Cette leçon vous montre comment les trouver et les corriger.
Comment le ramasse-miettes décide quoi conserver
Le ramasse-miettes d'Android conserve tout objet atteignable depuis une racine du ramasse-miettes (threads actifs, champs statiques, etc.). Tout objet inatteignable est libéré.
Une fuite est simplement un chemin d'accessibilité indésirable : un objet ayant une longue durée de vie conserve une référence vers un objet dont la durée de vie est courte. La démonstration purement Kotlin ci-dessous montre comment l'accessibilité maintient un objet en vie.
object GlobalCache {
val items = mutableListOf<ByteArray>()
}
fun cacheSomething() {
// This 1MB array stays alive forever because GlobalCache
// (a static singleton, a GC root) keeps referencing it.
GlobalCache.items.add(ByteArray(1_000_000))
}
fun main() {
repeat(3) { cacheSomething() }
println("Held arrays: ${GlobalCache.items.size}") // never freed
}Le grand classique : fuite d’une activité
La fuite Android la plus courante est celle d’un objet statique ou à longue durée de vie qui conserve un Context. Lorsque l’activité est détruite (rotation, navigation), le système veut la libérer, mais la référence statique la maintient en vie indéfiniment.
Le code ci-dessous provoque une fuite : un singleton met en cache le contexte d’une activité.
// LEAK: static field holds an Activity Context
object Analytics {
var context: Context? = null // <- keeps Activity alive
fun init(ctx: Context) { context = ctx }
}
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Analytics.init(this) // passing the Activity -> leak on rotation
}
}Correction : utiliser le contexte de l’application
Lorsque vous devez stocker un Context dans un objet à longue durée de vie, stockez le contexte de l’application, qui persiste pendant toute la durée de vie du processus et peut être conservé sans risque.
Ne conservez un contexte d’activité ou de vue que pendant l’existence de cet écran. Cette règle simple évite la plupart des fuites Android.
object Analytics {
private var appContext: Context? = null
fun init(ctx: Context) {
// applicationContext is process-scoped and safe to keep
appContext = ctx.applicationContext
}
}
// Call site is now leak-free:
Analytics.init(this) // stores applicationContext, not the ActivityClasses internes et gestionnaires
Une classe interne non statique (notamment la plupart des écouteurs, des Runnables et des rappels de Handler) conserve une référence implicite vers sa classe englobante. Si ce travail se poursuit après la disparition de l’écran, celui-ci fuit.
Un Handler.postDelayed différé est souvent en cause : le message en attente maintient l’activité en vie jusqu’à son exécution.
// LEAK: the posted Runnable holds the Activity for 60 seconds
handler.postDelayed({ updateUi() }, 60_000)
// FIX: cancel pending work when the screen goes away
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacksAndMessages(null)
}Coroutines : définir la portée de votre travail
Une coroutine qui se poursuit après la disparition de son écran provoque une fuite de tout ce qu’elle capture. La solution est la concurrence structurée : lancez le travail dans une portée liée à un cycle de vie afin qu’il soit automatiquement annulé.
Dans un ViewModel, utilisez viewModelScope ; dans un contrôleur d’interface utilisateur, utilisez lifecycleScope. Lorsque le propriétaire est détruit, la portée annule le travail et les références sont libérées.
class FeedViewModel : ViewModel() {
fun load() {
// Cancelled automatically when the ViewModel is cleared
viewModelScope.launch {
val feed = repository.fetchFeed()
_state.value = feed
}
}
}
// In Compose, collect tied to the lifecycle:
val state by viewModel.state.collectAsStateWithLifecycle()DisposableEffect dans Compose
Dans Compose, tout ce que vous enregistrez doit être désenregistré lorsque le composable disparaît. DisposableEffect fournit un bloc onDispose précisément prévu à cet effet : écouteurs, observateurs, capteurs et récepteurs de diffusion.
Oublier le nettoyage à cet endroit provoque une fuite du rappel et de tout ce qu’il capture.
@Composable
fun LocationDisplay(manager: LocationManager) {
DisposableEffect(manager) {
val listener = LocationListener { /* update */ }
manager.register(listener)
onDispose { manager.unregister(listener) } // prevents the leak
}
}Détecter les fuites avec LeakCanary
LeakCanary est l’outil standard pour détecter automatiquement les fuites dans les versions de débogage. Ajoutez la dépendance : l’outil surveille les objets détruits ; si l’un d’eux n’est pas récupéré par le ramasse-miettes, il extrait le tas et vous montre la chaîne de références exacte qui le maintient en vie.
Cette chaîne indique la correction à apporter : elle pointe directement vers le champ ou l’écouteur responsable.
// build.gradle.kts (app module)
dependencies {
debugImplementation("com.squareup.leakcanary:leakcanary-android:2.14")
}
// No code needed: on a debug build, navigate away from a screen and
// LeakCanary posts a notification with the leak trace, e.g.:
// Analytics.context -> MainActivity (leaked)Extraits du tas dans le profileur
Pour les fuites que LeakCanary ne signale pas (croissance lente, fuites natives, caches volumineux), utilisez le profileur de mémoire. Capturez un extrait du tas, forcez le GC et examinez les classes qui possèdent un nombre d’instances étonnamment élevé.
Une augmentation du nombre d’instances de votre activité, de votre fragment ou de votre ViewModel après avoir quitté cet écran est un indice irréfutable. Le profileur affiche également le chemin vers les racines du GC afin que vous puissiez suivre la référence.
Bitmaps et allocations volumineuses
Tout problème de mémoire n’est pas nécessairement une fuite de référence. Les bitmaps volumineux et les caches sans limite peuvent à eux seuls saturer le tas. Une photo en pleine résolution peut occuper plusieurs dizaines de mégaoctets en mémoire.
Demandez à une bibliothèque d’images (Coil/Glide) de réduire l’image à la taille affichée et imposez une limite explicite à tout cache que vous créez. L’extrait montre un cache LRU limité par sa taille.
// Bound memory: keep at most ~1/8 of available app memory
val maxKb = (Runtime.getRuntime().maxMemory() / 1024 / 8).toInt()
val bitmapCache = object : LruCache<String, Bitmap>(maxKb) {
override fun sizeOf(key: String, value: Bitmap) = value.byteCount / 1024
}
// An unbounded HashMap<String, Bitmap> would grow until OutOfMemoryError.Liste de contrôle pour prévenir les fuites
Adoptez ces habitudes pour éviter la plupart des fuites :
- Stockez applicationContext dans les objets à longue durée de vie, jamais une activité ou une vue.
- Exécutez le travail asynchrone dans une coroutine dont la portée est liée au cycle de vie (
viewModelScope,lifecycleScope). - Désenregistrez toujours les écouteurs et les récepteurs (
onDispose,onDestroy). - Annulez les Handlers différés et les minuteurs.
- Limitez les caches et réduisez la taille des bitmaps.
- Conservez LeakCanary dans les versions de débogage et agissez en fonction de ses traces.
Vérification rapide
Vous devez conserver un Context dans un objet singleton pendant toute sa durée de vie. Quel contexte doit-il conserver pour éviter de provoquer une fuite d’écran ?
Récapitulatif : garder une mémoire saine
Vous avez appris ce que sont les fuites et comment les éviter :
- Les fuites sont des chemins de référencement indésirables qui maintiennent en vie des objets qui devraient être détruits.
- La fuite classique est une référence statique ou à longue durée de vie vers une activité ou un contexte — utilisez plutôt applicationContext.
- Les écouteurs de classes internes, les Handlers différés et les coroutines sans portée font fuir leur propriétaire — définissez leur portée et annulez-les.
- Utilisez
DisposableEffectdans Compose pour désenregistrer les rappels. - LeakCanary et le profileur de mémoire révèlent la chaîne de références.
- Limitez les caches et réduisez la taille des bitmaps.
Ensuite : accélérer le démarrage de l’application grâce à l’optimisation du démarrage et aux profils de référence.
Questions Fréquemment Posées
La leçon « Fuites mémoire et corrections » est-elle gratuite ?
Oui — le texte complet de « Fuites mémoire et corrections » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Android Academy, passe à CoddyKit PRO. Le cours Android Academy comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « Fuites mémoire et corrections » ?
Trouvez et éliminez les fuites. Tu pratiques Android Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer Android Academy ?
Aucune expérience préalable n'est requise. Android Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 3 sur 4.
Combien de temps prend la leçon « Fuites mémoire et corrections » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon Android Academy ?
Oui. Chaque leçon Android Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- Mesurer les performances
- Maîtriser la recomposition
- Fuites mémoire et corrections
- Démarrage et profils de référence