Memory leaks og løsninger
Find og stop memory leaks.
Memory leaks og løsninger er en gratis Android Academy-lektion på CoddyKit. Dette er lektion 3 af 4. Du kan læse alle 3 lektioner i dette læringsspor gratis i deres fulde længde — derefter låser CoddyKit PRO alle lektioner op samt praktiske øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Den er en del af læringsforløbet i Android Academy, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Android Academy-kurset indeholder 4 lektioner i alt.
Hvad er en hukommelseslækage?
En hukommelseslækage opstår, når objekter, der ikke længere er nødvendige, ikke kan indsamles af affaldsindsamleren, fordi noget stadig har en reference til dem. På Android er det klassiske offer en Activity eller Context, der bliver hængende, efter at skærmen er ødelagt.
Lækager får din heap til at vokse over tid, udløser hyppigere affaldsindsamling (som sætter brugerfladen på pause og skaber hakken) og får til sidst appen til at gå ned med en OutOfMemoryError. Denne lektion viser, hvordan du finder og retter dem.
Sådan afgør GC, hvad der skal bevares
Androids affaldsindsamler bevarer alle objekter, der er nåbare fra en GC-rod (aktive tråde, statiske felter osv.). Alt, der ikke kan nås, frigives.
En lækage er ganske enkelt en uønsket nåbarhedssti: et objekt med lang levetid, der holder en reference til et objekt med kort levetid. Demonstrationen i ren Kotlin nedenfor viser, hvordan nåbarhed holder et objekt i live.
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
}Det klassiske eksempel: En lækkende Activity
Den mest almindelige Android-lækage opstår, når et statisk eller langlivet objekt holder på en Context. Når Activity'en ødelægges (ved rotation eller navigation), vil systemet frigive den, men den statiske reference holder den i live for altid.
Koden nedenfor lækker: en singleton gemmer en 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
}
}Løsning: Brug Application Context
Når du skal gemme en Context i noget langlivet, skal du gemme application context, som lever i hele processens levetid og kan bevares sikkert.
Behold kun en Activity-/View-context, så længe den pågældende skærm eksisterer. Denne ene regel forhindrer de fleste Android-lækager.
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 ActivityIndre klasser og Handlers
En ikke-statisk indre klasse (herunder de fleste lyttere, Runnables og Handler-callbacks) har en implicit reference til sin ydre klasse. Hvis arbejdet lever længere end skærmen, lækker det skærmen.
En forsinket Handler.postDelayed er en hyppig synder: Den ventende besked holder Activity'en i live, indtil den udløses.
// 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: Afgræns dit arbejde
En coroutine, der lever længere end sin skærm, lækker alt, hvad den har fanget. Løsningen er struktureret samtidighed: Start arbejdet i et scope, der er knyttet til en livscyklus, så det automatisk annulleres.
I en ViewModel skal du bruge viewModelScope; i en UI-controller skal du bruge lifecycleScope. Når ejeren ødelægges, annullerer scopet arbejdet, og referencerne frigives.
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 i Compose
I Compose skal alt, hvad du registrerer, afregistreres, når den komponerbare funktion forlader kompositionen. DisposableEffect giver dig en onDispose-blok præcis til dette: lyttere, observatører, sensorer og broadcast receivers.
Hvis du glemmer oprydningen her, lækker callbacken og alt, hvad den har fanget.
@Composable
fun LocationDisplay(manager: LocationManager) {
DisposableEffect(manager) {
val listener = LocationListener { /* update */ }
manager.register(listener)
onDispose { manager.unregister(listener) } // prevents the leak
}
}Find lækager med LeakCanary
LeakCanary er standardværktøjet til automatisk at finde lækager i debug-builds. Tilføj afhængigheden, så overvåger det ødelagte objekter; hvis et objekt ikke bliver garbage collected, dumper det heapen og viser dig den nøjagtige referencekæde, der holder det i live.
Den kæde viser løsningen: Den peger direkte på det problematiske felt eller den problematiske lytter.
// 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 i profileren
Ved lækager, som LeakCanary ikke markerer (langsom vækst, native lækager eller store caches), skal du bruge Memory Profiler. Tag et heap-dump, fremtving GC, og undersøg, hvilke klasser der har overraskende mange instanser.
Et voksende antal instanser af din Activity, Fragment eller ViewModel efter navigation væk fra skærmen er et klart faresignal. Profileren viser også stien til GC-rødderne, så du kan spore referencen.
Bitmaps og store allokeringer
Ikke alle hukommelsesproblemer skyldes en referencelækage. Store bitmaps og caches uden størrelsesgrænse kan fylde heapen helt af sig selv. Et foto i fuld opløsning kan fylde snesevis af megabytes i hukommelsen.
Lad et billedbibliotek (Coil/Glide) nedskalere billedet til den viste størrelse, og sæt en tydelig maksimumgrænse på alle caches, du opretter. Kodestykket viser en LRU-cache med begrænset størrelse.
// 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.Tjekliste til forebyggelse af lækager
Opbyg disse vaner, så opstår de fleste lækager aldrig:
- Gem applicationContext i langlivede objekter, aldrig en Activity eller View.
- Kør asynkront arbejde i en livscyklusafgrænset coroutine (
viewModelScope,lifecycleScope). - Afregistrér altid lyttere og receivere (
onDispose,onDestroy). - Annullér forsinkede Handlers og timere.
- Begræns caches, og nedskalér bitmaps.
- Behold LeakCanary i debug-builds, og reager på dets spor.
Hurtigt tjek
Du skal bruge et singleton-objekt til at gemme en Context i hele dets levetid. Hvilken context skal det indeholde for at undgå at lække en skærm?
Opsummering: Hold hukommelsen ren
Du har lært, hvad lækager er, og hvordan du stopper dem:
- Lækager er uønskede reachability-stier, der holder døde objekter i live.
- Den klassiske lækage er en statisk/langlivet reference til en Activity/Context — gem i stedet applicationContext.
- Lyttere i indre klasser, forsinkede Handlers og coroutines uden scope lækker deres ejer — afgræns og annullér dem.
- Brug
DisposableEffecti Compose til at afregistrere callbacks. - LeakCanary og Memory Profiler viser referencekæden.
- Begræns caches, og nedskalér bitmaps.
Næste emne: Sådan får du appen til at starte hurtigt med startup-optimering og baseline-profiler.
Lær Kotlin med en AI-underviser — gratis
Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.
- Kurser
- 36
- Lektioner
- 152
Ofte stillede spørgsmål
Er lektionen “Memory leaks og løsninger” gratis?
Ja — alle 3 lektioner i læringssporet Android Academy, inklusive “Memory leaks og løsninger”, kan læses gratis i deres fulde længde her på webstedet. Derefter låser CoddyKit PRO alle lektioner op samt interaktive øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Android Academy-kurset indeholder 4 lektioner i alt.
Hvad lærer jeg i “Memory leaks og løsninger”?
Find og stop memory leaks. Du øver dig i Android Academy med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.
Skal jeg have erfaring for at begynde på Android Academy?
Der kræves ingen tidligere erfaring. Android Academy på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 3 af 4.
Hvor lang tid tager lektionen “Memory leaks og løsninger”?
De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.
Kan jeg skrive og køre kode i denne Android Academy-lektion?
Ja. Alle Android Academy-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.
Alle lektioner i dette kursus
- Måling af ydeevne
- Styr på recomposition
- Memory leaks og løsninger
- Opstart og baseline-profiler