DisposableEffect — vad är det, frigöring av resurser i Jetpack Compose

Författare: IT Sectr Publicerad: 2026-06-30 Lästid: 9 min

DisposableEffect är en composable-funktion i Jetpack Compose avsedd för operationer som kräver explicit initiering och efterföljande rensning av resurser. Till skillnad från andra side-effect-API:er tillhandahåller DisposableEffect ett onDispose-block som garanterat körs när komponenten lämnar kompositionen eller när nyckeln ändras. Detta gör den oumbärlig för att arbeta med native-prenumerationer, sensor-lyssnare och hårdvaruresurser. Enligt Android Developers Documentation (2025) rekommenderas DisposableEffect i alla scenarier som kräver ett setup/teardown-par, analogt med onStart/onStop i Activitys livscykel.

Huvudpunkter

  • DisposableEffect — side-effect-API för konfigurering och garanterad rensning av resurser.
  • onDispose — obligatoriskt block som körs när man lämnar kompositionen eller när nyckeln ändras.
  • Synkronicitet — till skillnad från LaunchedEffect fungerar DisposableEffect synkront utan korutiner.
  • Rensning — typiska scenarier: avprenumeration från LiveData, stängning av sockets, avregistrering av BroadcastReceiver.
  • Nycklar — när nyckeln ändras körs onDispose för det gamla värdet och återinitiering med det nya.

Vad är DisposableEffect i Jetpack Compose

DisposableEffect är ett nyckelverktyg för resurshantering i Jetpack Compose. Dess huvudsakliga egenskap är det garanterade anropet av onDispose-blocket vid slutet av composable-komponentens livscykel. Detta beteende är kritiskt för Android-utveckling, där oavslutade prenumerationer på systemtjänster kan leda till minnesläckor och krascher av applikationen.

Till skillnad från LaunchedEffect, som fungerar i en asynkron korutinkontext, körs DisposableEffect synkront. Detta innebär att suspend-funktioner inte kan anropas inuti det. Synkronicitet säkerställer förutsägbarhet: du kan vara säker på att initieringskoden körs före den första renderingen och rensningskoden — innan komponenten tas bort från minnet.

Enligt Jetpack Compose-dokumentationen (2025) bör DisposableEffect användas i fyra huvudsakliga scenarier: (1) prenumeration på systemtjänster (sensorer, LocationManager), (2) registrering av BroadcastReceiver, (3) arbete med callback-baserade bibliotek som inte stöder korutiner, (4) koppling av Compose-komponenter till Legacy View-system via AndroidView.

kotlin
class SensorManager(private val context: Context) {
    fun startListening(callback: (Float) -> Unit) { /* register */ }
    fun stopListening() { /* cancel */ }
}

@Composable
fun SensorDisplay() {
    val sensorManager = remember { SensorManager(context) }
    var value by remember { mutableStateOf(0f) }
    
    DisposableEffect(Unit) {
        sensorManager.startListening { value = it }
        onDispose { sensorManager.stopListening() }
    }
    
    Text("Sensor: $value")
}

Hur DisposableEffect fungerar med onDispose

Den interna mekaniken för DisposableEffect är baserad på faserna i kompositionens livscykel. När en composable-komponent går in i kompositionen anropar DisposableEffect det överförda kodblocket. Detta block returnerar ett DisposableEffectResult-objekt som innehåller onDispose-lambdan. Kompositionen sparar detta resultat och anropar onDispose i det ögonblick komponenten lämnar kompositionen — oavsett orsak (navigering, förändring av förälderns tillstånd, borttagning från LazyColumn).

Nyckelmekanismen i DisposableEffect fungerar analogt med LaunchedEffect: när någon nyckel ändras körs först onDispose för det gamla tillståndet, sedan startas initieringsblocket om med de nya nycklarna. Detta gör det möjligt att omkonfigurera resursen när dess parametrar ändras. Om nyckeln till exempel är socketens URL, stängs den gamla socketen och en ny öppnas när den ändras.

Viktigt: onDispose-blocket är en obligatorisk del av DisposableEffect. Om onDispose inte anropas inuti blocket kommer koden inte att kompileras. Detta kompilatorkrav säkerställer att utvecklaren inte glömmer att förbereda rensning av resursen, vilket är en vanlig orsak till fel vid manuell hantering av prenumerationer.

kotlin
// Korrekt användning med en nyckel
DisposableEffect(sensorType) {
    val sensor = sensorManager.getDefaultSensor(sensorType)
    sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
    
    onDispose {
        sensorManager.unregisterListener(listener)
    }
}

// Flera resurser i en DisposableEffect
DisposableEffect(Unit) {
    context.registerReceiver(receiver, intentFilter)
    lifecycle.addObserver(observer)
    
    onDispose {
        context.unregisterReceiver(receiver)
        lifecycle.removeObserver(observer)
    }
}

DisposableEffect mot minnesläckor

Minnesläckor i Android-applikationer uppstår ofta på grund av oregistrerade lyssnare och prenumerationer som fortsätter att hålla en referens till Activity eller Context efter att skärmen har stängts. DisposableEffect löser detta problem på ramverksnivå: om utvecklaren har använt DisposableEffect för att registrera en lyssnare, kommer onDispose garanterat att avbryta prenumerationen i vilket scenario som helst av komponentens avslutning.

Detta är särskilt kritiskt för LazyColumn och LazyGrid, där element ständigt skapas och förstörs under scrollning. Utan DisposableEffect skulle varje element som försvinner från det synliga området lämna en aktiv prenumeration. Med DisposableEffect anropas onDispose för varje avlastat element, vilket säkerställer att resurser frigörs omedelbart efter att elementet lämnar skärmen.

Enligt Android Performance Patterns (Google, 2025) minskar användningen av DisposableEffect för alla native-prenumerationer antalet minnesläckor i Compose-applikationer med 60–70% jämfört med manuell hantering via livscykel-callbacks. Systemet självt spårar ögonblicket för att lämna kompositionen och garanterar exekvering av onDispose även vid nödstängning av skärmen.

ResursVad DisposableEffect görUtan DisposableEffect
BroadcastReceiverregister + onDispose → unregisterReceiver förblir aktiv
SensorManagerregisterListener + onDispose → unregisterSensor fortsätter skicka data
Observable (inte Flow)subscribe + onDispose → unregisterCallback håller referens
TextureView / SurfaceViewsetCallback + onDispose → removeCallbackCallback-läcka
Socket / Channelopen + onDispose → closeAnslutning förblir öppen

Prenumerera på sensorer via DisposableEffect

Ett av de mest illustrativa exemplen på användning av DisposableEffect — arbete med enhetssensorer (accelerometer, gyroskop, magnetometer). Sensorer kräver obligatorisk avregistrering vid arbetets slut, annars fortsätter de att förbruka batterienergi och skicka data även efter att skärmen stängts.

Praktiskt exempel: applikation för mätning av lutningsvinkel. DisposableEffect(Unit) registrerar en accelerometer-lyssnare när komponenten visas och avregistrerar i onDispose. Sensordata överförs till tillståndet via mutableStateOf, vilket automatiskt uppdaterar UI. Om skärmen scrollas i LazyColumn och elementet försvinner, aktiveras onDispose omedelbart — sensorn slutar skicka data för detta element.

När sensortypen ändras (till exempel från accelerometer till gyroskop) ändras sensorType-nyckeln, onDispose avbryter den gamla prenumerationen och det nya DisposableEffect-blocket registrerar den nya sensorn. Utan nycklar skulle du manuellt behöva kontrollera vilken sensor som tidigare registrerats och anropa unregisterListener med rätt listener — vilket är felbenäget.

kotlin
@Composable
fun SensorReadingScreen(sensorType: Int) {
    val context = LocalContext.current
    val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
    var sensorValue by remember { mutableStateOf(0f) }
    
    DisposableEffect(sensorType) {
        val sensor = sensorManager.getDefaultSensor(sensorType)
        val listener = SensorEventListener { event, _ ->
            sensorValue = event.values[0]
        }
        sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
        
        onDispose {
            sensorManager.unregisterListener(listener)
        }
    }
    
    Text("Värde: $sensorValue")
}

Registrera BroadcastReceiver via DisposableEffect

BroadcastReceiver är ett klassiskt exempel på ett API som kräver ett obligatoriskt register / unregister-par. I en Compose-applikation är DisposableEffect idealisk för att registrera en mottagare under en specifik skärms livstid. Vid inträde på skärmen registreras BroadcastReceiver med lämpligt IntentFilter, vid utträde — avbryts automatiskt i onDispose.

Typiskt scenario — övervakning av nätverksstatus. DisposableEffect registrerar en mottagare på ConnectivityManager som meddelar om förändringar i nätverksanslutningen. När status ändras (WiFi / mobildata / inget nätverk) uppdateras composable-tillståndet och UI visar motsvarande indikator. När skärmen stängs avbryter onDispose garanterat registreringen — även om applikationen går i bakgrunden.

För mottagare med ContextCompat.registerReceiver och flaggan RECEIVER_EXPORTED / RECEIVER_NOT_EXPORTED (Android 14+) blir användning av DisposableEffect obligatorisk, eftersom systemet kräver explicit angivelse av mottagarens verkningsdomän. DisposableEffect säkerställer att verkningsdomänen är begränsad till skärmens livstid, vilket överensstämmer med säkerhetskraven i nya Android-versioner.

kotlin
@Composable
fun NetworkStatusBanner() {
    val context = LocalContext.current
    var isConnected by remember { mutableStateOf(true) }
    
    DisposableEffect(Unit) {
        val receiver = BroadcastReceiver { _, _ ->
            val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
            isConnected = cm.getActiveNetwork() != null
        }
        IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION).let { filter ->
            context.registerReceiver(receiver, filter)
        }
        
        onDispose {
            context.unregisterReceiver(receiver)
        }
    }
    
    if (!isConnected) { ... }
}

Vanliga misstag med DisposableEffect

Det första kritiska misstaget — uteblivet anrop av onDispose. Koden inuti DisposableEffect-blocket måste anropa onDispose, annars uppstår ett kompileringsfel. Utvecklare försöker dock ibland kringgå detta genom att placera onDispose i ett villkor: if (condition) { onDispose { ... } }. Sådan kod kompileras, men onDispose registreras inte om villkoret inte uppfylls — resursen frigörs aldrig.

Det andra misstaget — användning av DisposableEffect för asynkrona operationer. Eftersom DisposableEffect är synkront kan delay()- eller await()-anrop inte skrivas inuti det. Om asynkron initiering med efterföljande rensning behövs, använd en kombination av LaunchedEffect (för dataladdning) och DisposableEffect (för konfigurering/rensning av native-resurser), eller en separat mekanism med rememberCoroutineScope.

Det tredje misstaget — att skapa nya objekt inuti DisposableEffect utan remember. Om objekt (sensor, listener, mottagare) skapas inuti effekten vid varje anrop och nycklarna ändras ofta, leder detta till överdriven objektskapning och sophämtning. Det är bättre att flytta objektskapandet till remember eller remember { ... } utanför DisposableEffect, och inuti effekten endast registrera och avbryta.

Vanliga frågor

Vad är skillnaden mellan DisposableEffect och LaunchedEffect?

DisposableEffect fungerar synkront och tillhandahåller onDispose för explicit rensning av resurser. LaunchedEffect fungerar asynkront i en korutin och avbryter den automatiskt vid nyckeländring eller utträde ur kompositionen. Om resursen kräver anrop av en cleanup-metod (close, unregister, dispose) — använd DisposableEffect. Om operationen är en suspend-funktion — använd LaunchedEffect.

Är onDispose-blocket i DisposableEffect obligatoriskt?

Ja, onDispose är obligatoriskt — Kotlin-kompilatorn kräver dess anrop inuti DisposableEffect-blocket. Om onDispose inte anropas kommer koden inte att kompileras. Detta är avsiktligt utformat för att förhindra utvecklares glömska och säkerställa att varje öppen resurs stängs när kompositionen lämnas.

Hur hanterar man fel inuti DisposableEffect?

Använd try-catch inuti DisposableEffect-blocket. Om registrering av resursen kan kasta ett undantag (till exempel sensorn hittades inte), omge det i try och hantera felet i UI via ett separat tillstånd. onDispose måste anropas oavsett framgången av initieringen — placera det i ett finally-block eller i slutet av try-sektionen.

Kan DisposableEffect användas för att prenumerera på Flow?

Rekommenderas inte. För Flow är det bättre att använda LaunchedEffect med collectLatest eller metoden .collectAsState() med Lifecycle.repeatOnLifecycle. DisposableEffect stöder inte suspend-funktioner, så en Flow-prenumeration inuti skulle kräva att en separat korutin startas via CoroutineScope, vilket komplicerar koden och ökar risken för läckor.

Hur många DisposableEffect kan finnas i en composable?

Det finns inga begränsningar, men det rekommenderas att gruppera relaterade resurser i en DisposableEffect med flera operationer inuti och en onDispose. Om resurserna är oberoende (till exempel sensor och BroadcastReceiver) är det bättre att dela upp dem i separata DisposableEffect med olika nycklar — detta förenklar felsökning och förhindrar oönskad återskapning av alla resurser när en nyckel ändras.

Sammanfattning

  • DisposableEffect — Jetpack Compose side-effect-API för synkron initiering med garanterad rensning via onDispose.
  • onDispose — obligatoriskt block som körs vid utträde ur kompositionen eller nyckeländring, vilket förhindrar minnesläckor.
  • Nycklar — när nyckeln ändras körs först onDispose för det gamla värdet, sedan återinitiering med det nya.
  • Synkronicitet — DisposableEffect körs synkront, suspend-funktioner är inte tillgängliga inuti det.
  • Typiska scenarier — BroadcastReceiver, sensorer, native-lyssnare, callback-baserade bibliotek, AndroidView-integration.
  • Läckor — DisposableEffect minskar antalet läckor med 60–70% jämfört med manuell hantering av livscykel-callbacks.
  • Misstag — huvudsakliga risker: villkorligt anrop av onDispose, användning för async-operationer, skapa objekt utan remember inuti effekten.

Vi utvecklar en mobil applikation nyckelfärdigt

IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.

Diskutera projektet

Läs också