Android Academy · Lektion

Tämja rekompositionering

Stabila parametrar och färre rekompositioneringar.

Lektion 2 av 413 steg

Tämja rekompositionering är en gratis lektion i Android Academy på CoddyKit. Detta är lektion 2 av 4. Du kan läsa vilka 3 lektioner som helst i den här lärvägen kostnadsfritt i sin helhet – därefter låser CoddyKit PRO upp alla lektioner, plus praktisk övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Den ingår i lärvägen för Android Academy, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i Android Academy innehåller totalt 4 lektioner.

Omkomposition: Compose-reglaget för prestanda

I Jetpack Compose beskrivs UI:t med funktioner. När tillståndet ändras sker en omkomposition i Compose: de composables som läser tillståndet körs igen för att uppdatera skärmen.

Omkomposition är normalt och billigt när det är väl avgränsat. Men när den sker för ofta eller över för stora delar av trädet blir den den främsta orsaken till jank i Compose. I den här lektionen lär ni er att hålla omkompositionen liten och sällsynt.

Se antal omkompositioner

Mät innan ni åtgärdar problemet. Layout Inspector i Android Studio visar antalet omkompositioner per composable i realtid. Ni kan också använda Compose-kompilatorns mätvärden eller en enkel räknare för felsökning.

Ett enkelt knep är att låta en SideEffect öka en räknare varje gång en composable omkomponeras, så att ni kan logga oväntade resultat under utvecklingen.

@Composable
fun RecompositionCounter(tag: String) {
    val count = remember { mutableStateOf(0) }
    SideEffect { count.value++ }
    Log.d("Recompose", "$tag recomposed ${count.value} times")
}

// Drop RecompositionCounter("PriceLabel") inside a composable
// to watch how often it re-runs while you interact.

Läs tillstånd så sent som möjligt

Compose omkomponerar bara de composables som läser ett tillståndsvärde. Om en förälder läser tillståndet omkomponeras hela föräldern; om bara ett litet barn läser det omkomponeras endast barnet.

Flytta därför tillståndsläsningen nedåt i trädet. I den dåliga versionen omkomponeras hela Column vid varje tick; den bra versionen begränsar det till etiketten.

// BAD: Column reads `seconds`, so everything recomposes each second
@Composable
fun TimerBad(seconds: Int) {
    Column {
        ExpensiveHeader()
        Text("Elapsed: $seconds")
    }
}

// GOOD: only the Text reads the value via a lambda
@Composable
fun TimerGood(seconds: () -> Int) {
    Column {
        ExpensiveHeader()
        Text("Elapsed: ${seconds()}")
    }
}

Skjut upp läsningar med lambdas

Ett kraftfullt mönster är att i stället för att skicka ett värde som ändras ofta skicka en lambda som returnerar värdet. Den composable som till slut anropar lambdan är den enda som omkomponeras.

Det är därför Modifier.offset { ... } och graphicsLayer { ... } tar emot lambdas: rullnings- och animationsvärden ändras varje bildruta, och lambdan håller omkompositionen borta från layoutfasen helt och hållet.

// Passing the value: parent recomposes every frame of scroll
Box(Modifier.offset(y = scrollOffset.dp))

// Passing a lambda: skips recomposition, updates in the layout phase
Box(Modifier.offset { IntOffset(x = 0, y = scrollOffset.roundToInt()) })

// Same idea for alpha/scale during animation:
Image(
    painter = painter,
    contentDescription = null,
    modifier = Modifier.graphicsLayer { alpha = animatedAlpha() }
)

Stabilitet: varför Compose kan hoppa över

Compose kan hoppa över omkompositionen av en composable om alla dess parametrar är stabila och oförändrade. En typ är stabil när Compose kan lita på att equals återspeglar verkliga ändringar och att dess publika fält inte ändras utan att det märks.

  • Stabila: primitiva typer, String, oföränderliga dataklasser och State.
  • Instabila: gränssnitten List/Map, klasser med fält av typen var samt typer från moduler utan kompilatorn.

En instabil parameter tvingar fram omkomposition även när inget har ändrats.

// UNSTABLE: List is an interface; Compose cannot assume immutability,
// so UserList recomposes even if the contents are identical.
@Composable
fun UserList(users: List<User>) { /* ... */ }

data class User(val id: Long, val name: String) // stable: all vals

Gör parametrarna stabila

Två vanliga lösningar på instabila parametrar:

  • Använd oföränderliga samlingar från kotlinx.collections.immutable (till exempel ImmutableList), som Compose behandlar som stabila.
  • Annotera en klass som ni kontrollerar med @Immutable eller @Stable för att lova Compose att den inte ändras.

Nu kan Compose säkert hoppa över omkompositionen när samma instans skickas igen.

import kotlinx.collections.immutable.ImmutableList
import androidx.compose.runtime.Immutable

@Immutable
data class UiState(
    val title: String,
    val users: ImmutableList<User>
)

// Stable parameter -> Compose can skip this when state is unchanged
@Composable
fun UserList(users: ImmutableList<User>) { /* ... */ }

remember: beräkna inte om vid varje bildruta

Composables kan köras många gånger. Alla beräkningar som inte är triviala och görs direkt i kroppen körs vid varje omkomposition. Omslut dem i remember så att de bara beräknas om när deras nycklar ändras.

För värden som härleds från annat tillstånd bör ni föredra derivedStateOf, som bara avger ett nytt värde när det beräknade resultatet faktiskt ändras.

// Recomputes the sorted list only when `items` changes
val sorted = remember(items) { items.sortedBy { it.name } }

// derivedStateOf: only triggers readers when the BOOLEAN flips,
// not on every scroll pixel
val showButton by remember {
    derivedStateOf { listState.firstVisibleItemIndex > 5 }
}
if (showButton) ScrollToTopButton()

Stabila nycklar i lata listor

I LazyColumn/LazyRow ska ni ge varje objekt en stabil nyckel. Utan nycklar tvingar insättning eller omordning Compose att omkomponera och mäta om objekt som egentligen inte har ändrats, eftersom de spåras utifrån sin position.

Med en stabil nyckel kan Compose matcha objekten mellan uppdateringar och hoppa över de oförändrade.

LazyColumn {
    items(
        items = users,
        key = { user -> user.id }   // stable identity
    ) { user ->
        UserRow(user)
    }
}
// Now adding a user at the top reuses existing rows
// instead of recomposing the whole list.

Flytta upp tillståndet, skicka händelser uppåt

Instabila lambda-parametrar kan också förhindra överhoppning om en ny lambdainstans skapas vid varje omkomposition. Stabilisera återanrop genom att komma ihåg dem eller referera till stabila funktioner.

Tillsammans med tillståndslyftning (tillståndet finns hos anroparen och händelser flödar uppåt) håller detta bladkomponenterna billiga och möjliga att hoppa över.

@Composable
fun SearchBar(query: String, onQueryChange: (String) -> Unit) {
    TextField(value = query, onValueChange = onQueryChange)
}

// In the caller, a remembered lambda keeps the reference stable:
val onChange = remember { { newValue: String -> viewModel.setQuery(newValue) } }
SearchBar(query = query, onQueryChange = onChange)

Undvik att läsa rullnings- och animationstillstånd för högt upp

Ett klassiskt misstag är att läsa ett snabbt föränderligt tillstånd (rullningsförskjutning eller animationsförlopp) i en composable högt upp i hierarkin. Det tvingar hela underträdet att omkomponeras vid varje bildruta.

Håll sådana läsningar i Modifier-lambdas (offset {}, graphicsLayer {}, drawBehind {}) så att arbetet sker i layout- eller ritfasen, inte under omkomposition. Det är skillnaden mellan jämn rullning i 60 bilder per sekund och hackig rullning.

// Animated color used only for drawing -> stay in the draw phase
Box(
    Modifier.drawBehind {
        drawRect(color = animatedColor())  // lambda read, no recomposition
    }
)

Checklista för omkomposition

När en skärm känns hackig under interaktion går ni igenom den här listan:

  • Är parametrarna stabila (oföränderliga data, ImmutableList)?
  • Läser ni snabbt föränderligt tillstånd så långt ned som möjligt, helst i modifierlambdas?
  • Har lata listor stabila nycklar?
  • Är dyra beräkningar placerade bakom remember / derivedStateOf?
  • Bekräftade Layout Inspector att antalet faktiskt minskade?

Varje markerad punkt tar bort onödigt arbete från varje bildruta.

Snabbkontroll

Ni skickar en List<User> till en composable och märker att den omkomponeras även när datan är oförändrad. Vad är den mest direkta lösningen?

Sammanfattning: mindre och mer sällsynt omkomposition

Ni har lärt er att bemästra omkomposition, det främsta prestandaproblemet i Compose:

  • Mät antalet med Layout Inspector eller en felsökningsräknare.
  • Läs tillstånd så långt ned som möjligt; skjut upp läsningar med lambdas.
  • Gör parametrarna stabila med oföränderliga data och @Immutable/ImmutableList.
  • Använd remember och derivedStateOf för att undvika omberäkning vid varje bildruta.
  • Ge lata objekt stabila nycklar och håll läsningar av snabbt föränderligt tillstånd i modifierlambdas.

Nästa steg är att gå från CPU och omkomposition till minne: att hitta och åtgärda läckor.

Gratis att börja

Lär dig Kotlin med en AI-lärare – gratis

Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.

Kurser
36
Lektioner
152

Vanliga frågor

Är lektionen ”Tämja rekompositionering” gratis?

Ja – du kan läsa vilka 3 lektioner som helst i lärvägen Android Academy, inklusive ”Tämja rekompositionering”, kostnadsfritt i sin helhet här på webben. Därefter låser CoddyKit PRO upp alla lektioner, plus interaktiv övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Kursen i Android Academy innehåller totalt 4 lektioner.

Vad lär jag mig i ”Tämja rekompositionering”?

Stabila parametrar och färre rekompositioneringar. Ni övar på Android Academy med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.

Behöver jag någon erfarenhet för att börja lära mig Android Academy?

Du behöver inga förkunskaper. Utbildningen i Android Academy på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 2 av 4.

Hur lång tid tar lektionen ”Tämja rekompositionering”?

De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.

Kan jag skriva och köra kod i den här Android Academy-lektionen?

Ja. Varje Android Academy-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.

Alla lektioner i den här kursen

  1. Mäta prestanda
  2. Tämja rekompositionering
  3. Minnesläckor och åtgärder
  4. Start och baslinjeprofiler
← Tillbaka till Android Academy