Android Academy · Les

Geheugenlekken en oplossingen

Vind lekken en stop ze.

Les 3 van 413 stappen

Geheugenlekken en oplossingen is een gratis Android Academy-les op CoddyKit. Dit is les 3 van 4. Je kunt 3 lessen uit dit leerpad gratis volledig lezen — daarna ontgrendelt CoddyKit PRO alle lessen, plus praktische oefeningen met een ingebouwde code-editor en een AI-tutor die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject Android Academy. Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus Android Academy bevat in totaal 4 lessen.

Wat is een geheugenlek?

Een geheugenlek ontstaat wanneer objecten die niet langer nodig zijn niet door de garbagecollector kunnen worden opgeruimd, omdat iets nog steeds een verwijzing naar die objecten bevat. Op Android is een Activity of Context die blijft bestaan nadat het scherm is vernietigd het klassieke slachtoffer.

Door lekken groeit je heap na verloop van tijd, wordt garbagecollection vaker gestart (waardoor de UI pauzeert en haperingen ontstaan) en crasht de app uiteindelijk met een OutOfMemoryError. In deze les leer je hoe je ze vindt en oplost.

Hoe de garbagecollector bepaalt wat behouden blijft

De garbagecollector van Android behoudt elk object dat bereikbaar is vanaf een GC-root (actieve threads, statische velden enzovoort). Alles wat niet bereikbaar is, wordt vrijgegeven.

Een lek is simpelweg een ongewenst bereikbaarheids pad: een object met een lange levensduur dat een verwijzing naar een object met een korte levensduur vasthoudt. De demonstratie in zuiver Kotlin hieronder laat zien hoe bereikbaarheid een object in leven houdt.

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
}

De klassieker: een Activity laten lekken

Het meest voorkomende Android-lek ontstaat wanneer een statisch of langlevend object een Context vasthoudt. Wanneer de Activity wordt vernietigd, bijvoorbeeld door draaien of navigeren, wil het systeem deze vrijgeven, maar de statische verwijzing houdt haar voor altijd in leven.

De onderstaande code veroorzaakt een lek: een singleton slaat een Activity-context op.

// 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
    }
}

Oplossing: gebruik de Application Context

Wanneer je een Context moet opslaan in iets dat lang blijft bestaan, sla dan de application context op. Deze leeft gedurende het hele proces en kan veilig worden vastgehouden.

Houd een Activity/View-context alleen vast zolang dat scherm bestaat. Met deze ene regel voorkom je de meeste Android-lekken.

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 Activity

Innerlijke klassen en Handlers

Een niet-statische innerlijke klasse, waaronder de meeste listeners, Runnables en Handler-callbacks, bevat een impliciete verwijzing naar de omringende klasse. Als dat werk langer blijft bestaan dan het scherm, lekt het scherm.

Een vertraagde Handler.postDelayed is een veelvoorkomende boosdoener: het wachtende bericht houdt de Activity in leven totdat het wordt uitgevoerd.

// 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: geef je werk een scope

Een coroutine die langer blijft bestaan dan het scherm, lekt alles wat deze heeft vastgelegd. De oplossing is gestructureerde concurrency: start werk in een scope die aan een levenscyclus is gekoppeld, zodat het automatisch wordt geannuleerd.

Gebruik in een ViewModel viewModelScope en in een UI-controller lifecycleScope. Wanneer de eigenaar wordt vernietigd, annuleert de scope en worden verwijzingen vrijgegeven.

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 in Compose

In Compose moet je alles wat je registreert, weer afmelden wanneer de composable verdwijnt. DisposableEffect biedt hiervoor precies een blok onDispose: voor listeners, observers, sensoren en broadcast receivers.

Als je het opruimen hier vergeet, lekken de callback en alles wat deze heeft vastgelegd.

@Composable
fun LocationDisplay(manager: LocationManager) {
    DisposableEffect(manager) {
        val listener = LocationListener { /* update */ }
        manager.register(listener)
        onDispose { manager.unregister(listener) } // prevents the leak
    }
}

Lekken vinden met LeakCanary

LeakCanary is de standaardtool om lekken automatisch te detecteren in debug-builds. Voeg de afhankelijkheid toe; de tool houdt vernietigde objecten in de gaten. Als een object niet door de garbagecollector wordt opgeruimd, maakt de tool een heapdump en toont deze de exacte verwijzingsketen waardoor het object in leven blijft.

Die keten wijst je naar de oplossing: deze leidt rechtstreeks naar het problematische veld of de problematische listener.

// 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)

Heapdumps in de profiler

Gebruik voor lekken die LeakCanary niet signaleert, zoals geleidelijke groei, native lekken of grote caches, de Memory Profiler. Maak een heapdump, forceer garbagecollection en bekijk welke klassen verrassend veel instanties hebben.

Een groeiend aantal instanties van je Activity, Fragment of ViewModel nadat je bent wegge navigeerd, is een duidelijk alarmsignaal. De profiler toont ook het pad naar de GC-roots, zodat je de verwijzing kunt volgen.

Bitmaps en grote geheugentoewijzingen

Niet elk geheugenprobleem is een lek van verwijzingen. Grote bitmaps en caches zonder bovengrens kunnen op zichzelf de heap vullen. Een foto in volledige resolutie kan tientallen megabytes geheugen innemen.

Laat een afbeeldingsbibliotheek (Coil/Glide) de afbeelding verkleinen naar het weergegeven formaat en geef elke cache die je maakt een expliciete maximale grootte. Het fragment toont een LRU-cache met een begrensde grootte.

// 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.

Controlelijst om lekken te voorkomen

Maak van deze gewoonten een vast onderdeel van je werkwijze, zodat de meeste lekken nooit ontstaan:

  • Sla in langlevende objecten applicationContext op, nooit een Activity/View.
  • Voer asynchroon werk uit in een coroutine met een lifecycle-scope (viewModelScope, lifecycleScope).
  • Meld listeners en receivers altijd weer af (onDispose, onDestroy).
  • Annuleer vertraagde Handlers en timers.
  • Begrens caches en verklein bitmaps.
  • Houd LeakCanary in debug-builds en onderneem actie op basis van de traces.

Korte controle

Je hebt een singleton-object nodig dat gedurende zijn hele levensduur een Context vasthoudt. Welke context moet het vasthouden om te voorkomen dat een scherm lekt?

Samenvatting: houd je geheugen schoon

Je hebt geleerd wat lekken zijn en hoe je ze voorkomt:

  • Lekken zijn ongewenste paden van bereikbaarheid waardoor dode objecten in leven blijven.
  • Het klassieke lek is een statische of langlevende verwijzing naar een Activity/Context — sla in plaats daarvan applicationContext op.
  • Listeners in innerlijke klassen, vertraagde Handlers en coroutines zonder scope lekken hun eigenaar — geef ze een scope en annuleer ze.
  • Gebruik DisposableEffect in Compose om callbacks af te melden.
  • LeakCanary en de Memory Profiler maken de verwijzingsketen zichtbaar.
  • Begrens caches en verklein bitmaps.

Hierna: de app snel laten starten met startupoptimalisatie en baselineprofielen.

Gratis beginnen

Leer Kotlin met een AI-tutor — gratis

Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.

Cursussen
36
Lessen
152

Veelgestelde vragen

Is de les “Geheugenlekken en oplossingen” gratis?

Ja — je kunt hier op het web alle 3 lessen van het leerpad Android Academy, waaronder “Geheugenlekken en oplossingen”, gratis volledig lezen. Daarna ontgrendelt CoddyKit PRO alle lessen, plus interactieve oefeningen met een ingebouwde code-editor en een AI-tutor die 24/7 beschikbaar is. De cursus Android Academy bevat in totaal 4 lessen.

Wat leer ik in “Geheugenlekken en oplossingen”?

Vind lekken en stop ze. Je oefent met Android Academy door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.

Heb ik ervaring nodig om met Android Academy te beginnen?

Ervaring vooraf is niet nodig. Android Academy op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 3 van 4.

Hoe lang duurt de les “Geheugenlekken en oplossingen”?

De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.

Kan ik code schrijven en uitvoeren in deze les over Android Academy?

Ja. Elke les over Android Academy bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.

Alle lessen in deze cursus

  1. Prestaties meten
  2. Recompositie onder controle krijgen
  3. Geheugenlekken en oplossingen
  4. Opstarten en baselineprofielen
← Terug naar Android Academy