0Pricing
Android Academy · Lektion

Memory Leaks und ihre Behebung

Leaks finden und verhindern

Memory Leaks und ihre Behebung ist eine kostenlose Android Academy-Lektion auf CoddyKit. Dies ist Lektion 3 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des Android Academy-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der Android Academy-Kurs umfasst insgesamt 4 Lektionen.

Was ist ein Memory-Leak?

Ein Memory-Leak tritt auf, wenn nicht mehr benötigte Objekte nicht durch die Garbage Collection entfernt werden können, weil noch etwas eine Referenz auf sie hält. Unter Android ist ein Activity oder Context, das nach der Zerstörung seines Bildschirms weiterbesteht, das klassische betroffene Objekt.

Leaks vergrößern mit der Zeit den Heap, lösen häufiger eine Garbage Collection aus (die die UI pausiert und Jank verursacht) und führen schließlich zum Absturz der App mit einem OutOfMemoryError. Diese Lektion zeigt, wie Sie sie finden und beheben.

Wie die GC entscheidet, was behalten wird

Der Garbage Collector von Android behält jedes Objekt, das von einer GC-Root aus erreichbar ist (aktive Threads, statische Felder usw.). Nicht erreichbare Objekte werden freigegeben.

Ein Leak ist schlicht ein unerwünschter Erreichbarkeitspfad: Ein langlebiges Objekt hält eine Referenz auf ein kurzlebiges Objekt. Die folgende Demo in reinem Kotlin zeigt, wie Erreichbarkeit ein Objekt am Leben hält.

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
}

Der Klassiker: Ein Activity-Leak

Das häufigste Android-Leak entsteht, wenn ein statisches oder langlebiges Objekt einen Context festhält. Wenn die Activity zerstört wird (etwa durch Rotation oder Navigation), möchte das System sie freigeben. Die statische Referenz hält sie jedoch dauerhaft am Leben.

Der folgende Code verursacht ein Leak: Ein Singleton cached einen Activity-Context.

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

Behebung: Den Application Context verwenden

Wenn Sie einen Context in etwas Langlebigem speichern müssen, speichern Sie den Application Context. Dieser lebt für die gesamte Prozessdauer und kann sicher behalten werden.

Behalten Sie einen Activity-/View-Context nur so lange, wie der jeweilige Bildschirm existiert. Diese eine Regel verhindert die meisten Android-Leaks.

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

Innere Klassen und Handler

Eine nicht-statische innere Klasse (einschließlich der meisten Listener, Runnables und Handler-Callbacks) enthält eine implizite Referenz auf ihre äußere Klasse. Wenn diese Arbeit länger als der Bildschirm existiert, verursacht sie ein Leak des Bildschirms.

Ein verzögertes Handler.postDelayed ist ein häufiger Verursacher: Die ausstehende Nachricht hält die Activity am Leben, bis sie ausgeführt wird.

// 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: Arbeit an einen Scope binden

Eine Coroutine, die länger als ihr Bildschirm lebt, verursacht ein Leak von allem, was sie erfasst. Die Lösung ist strukturierte Nebenläufigkeit: Starten Sie die Arbeit in einem an einen Lifecycle gebundenen Scope, damit sie automatisch abgebrochen wird.

Verwenden Sie in einem ViewModel viewModelScope und in einem UI-Controller lifecycleScope. Wenn der Besitzer zerstört wird, bricht der Scope ab und Referenzen werden freigegeben.

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 muss alles, was Sie registrieren, wieder abgemeldet werden, wenn das Composable entfernt wird. DisposableEffect stellt dafür einen onDispose-Block bereit: für Listener, Observer, Sensoren und Broadcast Receiver.

Wenn Sie die Bereinigung hier vergessen, verursachen Sie ein Leak des Callbacks und von allem, was dieser erfasst.

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

Leaks mit LeakCanary finden

LeakCanary ist das Standardtool, um Leaks in Debug-Builds automatisch zu erkennen. Fügen Sie die Abhängigkeit hinzu. LeakCanary überwacht zerstörte Objekte. Wird eines nicht durch Garbage Collection entfernt, erstellt es einen Heap Dump und zeigt Ihnen die genaue Referenzkette, die das Objekt am Leben hält.

Diese Kette liefert den Ansatz für die Behebung: Sie verweist direkt auf das problematische Feld oder den problematischen 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)

Heap Dumps im Profiler

Verwenden Sie bei Leaks, die LeakCanary nicht erkennt (langsames Wachstum, native Speicherbelegung oder große Caches), den Memory Profiler. Erstellen Sie einen Heap Dump, erzwingen Sie eine GC und prüfen Sie, von welchen Klassen überraschend viele Instanzen existieren.

Eine steigende Anzahl von Instanzen Ihrer Activity, Ihres Fragments oder Ihres ViewModels nach dem Wegnavigieren ist ein eindeutiges Warnsignal. Der Profiler zeigt außerdem den Pfad zu den GC Roots, sodass Sie die Referenz zurückverfolgen können.

Bitmaps und große Speicherbelegungen

Nicht jedes Speicherproblem ist ein Referenz-Leak. Große Bitmaps und unbegrenzte Caches können den Heap auch unabhängig davon überlasten. Ein Foto in voller Auflösung kann im Speicher mehrere Dutzend Megabyte belegen.

Lassen Sie eine Bildbibliothek (Coil/Glide) die Bilder auf die angezeigte Größe herunterskalieren, und begrenzen Sie jeden von Ihnen erstellten Cache mit einem expliziten Maximum. Das Snippet zeigt einen größenbegrenzten LRU-Cache.

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

Checkliste zur Leak-Vermeidung

Etablieren Sie diese Gewohnheiten, dann entstehen die meisten Leaks gar nicht erst:

  • Speichern Sie in langlebigen Objekten applicationContext, niemals eine Activity oder einen View.
  • Führen Sie asynchrone Arbeit in einer Lifecycle-gebundenen Coroutine aus (viewModelScope, lifecycleScope).
  • Melden Sie Listener und Receiver immer ab (onDispose, onDestroy).
  • Brechen Sie verzögerte Handler und Timer ab.
  • Begrenzen Sie Caches und skalieren Sie Bitmaps herunter.
  • Behalten Sie LeakCanary in Debug-Builds bei und reagieren Sie auf die von ihm angezeigten Referenzpfade.

Kurzer Test

Sie benötigen ein Singleton-Objekt, das einen Context für seine gesamte Lebensdauer speichert. Welchen Context sollte es enthalten, damit kein Leak eines Bildschirms entsteht?

Zusammenfassung: Speicher sauber halten

Sie haben gelernt, was Leaks sind und wie Sie sie verhindern:

  • Leaks sind unerwünschte Erreichbarkeitspfade, die nicht mehr benötigte Objekte am Leben halten.
  • Das klassische Leak ist eine statische oder langlebige Referenz auf eine Activity/einen Context – verwenden Sie stattdessen applicationContext.
  • Listener innerer Klassen, verzögerte Handler und nicht an einen Scope gebundene Coroutines verursachen ein Leak ihres Besitzers – binden Sie sie an einen Scope und brechen Sie sie ab.
  • Verwenden Sie in Compose DisposableEffect, um Callbacks abzumelden.
  • LeakCanary und der Memory Profiler machen die Referenzkette sichtbar.
  • Begrenzen Sie Caches und skalieren Sie Bitmaps herunter.

Als Nächstes: die App mit Startup-Optimierung und Baseline Profiles schnell starten.

Häufig gestellte Fragen

Ist die Lektion „Memory Leaks und ihre Behebung“ kostenlos?

Ja — der vollständige Text von „Memory Leaks und ihre Behebung“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des Android Academy-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der Android Academy-Kurs umfasst insgesamt 4 Lektionen.

Was lerne ich in „Memory Leaks und ihre Behebung“?

Leaks finden und verhindern Du übst Android Academy mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.

Brauche ich Erfahrung, um Android Academy zu starten?

Keine Vorkenntnisse erforderlich. Android Academy auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 3 von 4.

Wie lange dauert die Lektion „Memory Leaks und ihre Behebung“?

Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.

Kann ich in dieser Android Academy-Lektion Code schreiben und ausführen?

Ja. Jede Android Academy-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.

Alle Lektionen in diesem Kurs

  1. Performance messen
  2. Recomposition bändigen
  3. Memory Leaks und ihre Behebung
  4. Startup- und Baseline-Profile
← Zurück zu Android Academy