0Pricing
Android Academy · Lektion

Recomposition bändigen

Stabile Parameter und weniger Recompositions

Recomposition bändigen ist eine kostenlose Android Academy-Lektion auf CoddyKit. Dies ist Lektion 2 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.

Recomposition: der Compose-Performance-Regler

In Jetpack Compose wird die UI durch Funktionen beschrieben. Wenn sich der Zustand ändert, führt Compose eine Recomposition aus: Es führt die Composables, die diesen Zustand lesen, erneut aus, um den Bildschirm zu aktualisieren.

Recomposition ist normal und kostengünstig, wenn sie gut eingegrenzt ist. Wenn sie jedoch zu häufig oder für zu große Teile des Baums stattfindet, wird sie zur häufigsten Ursache für Compose-Jank. In dieser Lektion lernen Sie, Recompositions klein und selten zu halten.

Recomposition-Zähler anzeigen

Messen Sie zuerst, bevor Sie etwas beheben. Der Layout Inspector in Android Studio zeigt die Anzahl der Recompositions pro Composable in Echtzeit an. Sie können auch die Metriken des Compose-Compilers oder einen einfachen Debug-Zähler verwenden.

Ein einfacher Trick: Ein SideEffect erhöht bei jeder Recomposition eines Composables einen Zähler, sodass Sie während der Entwicklung unerwartete Recompositions protokollieren können.

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

Zustand so spät wie möglich lesen

Compose führt nur die Composables erneut aus, die einen Zustandswert lesen. Wenn ein übergeordnetes Composable den Zustand liest, wird das gesamte übergeordnete Composable erneut ausgeführt. Wenn nur ein kleines untergeordnetes Composable ihn liest, wird nur dieses erneut ausgeführt.

Verschieben Sie Zustandslesevorgänge daher im Baum nach unten. In der fehlerhaften Version wird bei jedem Tick die gesamte Column erneut ausgeführt; die gute Version beschränkt dies auf das Label.

// 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()}")
    }
}

Lesen mit Lambdas verzögern

Ein leistungsfähiges Muster: Übergeben Sie statt eines häufig veränderten Werts ein Lambda, das diesen Wert zurückgibt. Nur das Composable, das das Lambda schließlich aufruft, wird erneut ausgeführt.

Deshalb erwarten Modifier.offset { ... } und graphicsLayer { ... } Lambdas: Scroll- und Animationswerte ändern sich in jedem Frame, und das Lambda hält Recompositions vollständig aus der Layout-Phase heraus.

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

Stabilität: Warum Compose überspringt

Compose kann die erneute Ausführung eines Composables überspringen, wenn alle seine Parameter stabil und unverändert sind. Ein Typ ist stabil, wenn Compose darauf vertrauen kann, dass equals echte Änderungen widerspiegelt und seine öffentlichen Felder nicht unbemerkt verändert werden.

  • Stabil: primitive Typen, String, unveränderliche Data Classes, State.
  • Instabil: die Schnittstellen List/Map, Klassen mit var-Feldern, Typen aus Modulen ohne Compiler-Unterstützung.

Ein instabiler Parameter erzwingt eine Recomposition, selbst wenn sich nichts geändert hat.

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

Parameter stabil machen

Zwei gängige Lösungen für instabile Parameter:

  • Verwenden Sie unveränderliche Collections aus kotlinx.collections.immutable (z. B. ImmutableList), die Compose als stabil behandelt.
  • Annotieren Sie eine von Ihnen kontrollierte Klasse mit @Immutable oder @Stable, um Compose zuzusichern, dass sie nicht verändert wird.

Nun kann Compose die Recomposition sicher überspringen, wenn dieselbe Instanz erneut übergeben wird.

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: Nicht in jedem Frame neu berechnen

Composables können sehr oft ausgeführt werden. Jede nicht triviale Berechnung, die direkt im Body erfolgt, wird bei jeder Recomposition ausgeführt. Verpacken Sie sie in remember, damit sie nur neu berechnet wird, wenn sich ihre Schlüssel ändern.

Für aus anderem Zustand abgeleitete Werte sollten Sie derivedStateOf bevorzugen. Es gibt einen Wert nur dann erneut aus, wenn sich das berechnete Ergebnis tatsächlich ändert.

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

Stabile Schlüssel in Lazy-Listen

Geben Sie in LazyColumn/LazyRow jedem Element einen stabilen key. Ohne Schlüssel zwingt das Einfügen oder Umsortieren von Elementen Compose dazu, Elemente, die sich eigentlich nicht geändert haben, erneut auszuführen und zu messen, weil sie anhand ihrer Position verfolgt werden.

Mit einem stabilen Schlüssel kann Compose Elemente über Aktualisierungen hinweg zuordnen und unveränderte Elemente überspringen.

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.

Zustand nach oben heben, Ereignisse nach oben weitergeben

Instabile Lambda-Parameter können das Überspringen ebenfalls verhindern, wenn bei jeder Recomposition eine neue Lambda-Instanz erstellt wird. Stabilisieren Sie Callbacks, indem Sie sie sich merken oder auf stabile Funktionen verweisen.

Zusammen mit State Hoisting (der Zustand liegt beim aufrufenden Composable, Ereignisse fließen nach oben) hält dies Leaf-Composables kostengünstig und überspringbar.

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

Scroll- und Animationszustand nicht zu weit oben lesen

Ein klassischer Fehler: Einen sich schnell ändernden Zustand (Scroll-Offset, Animationsfortschritt) in einem übergeordneten Composable lesen. Dadurch wird der gesamte Teilbaum in jedem Frame erneut ausgeführt.

Lesen Sie solche Werte innerhalb von Modifier-Lambdas (offset {}, graphicsLayer {}, drawBehind {}), damit die Arbeit in der Layout- oder Zeichenphase stattfindet und nicht während der Recomposition. Das ist der Unterschied zwischen flüssigem Scrollen mit 60 fps und ruckeligem Scrollen.

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

Checkliste für Recompositions

Wenn sich ein Bildschirm bei der Interaktion ruckelig anfühlt, gehen Sie diese Liste durch:

  • Sind die Parameter stabil (unveränderliche Daten, ImmutableList)?
  • Lesen Sie sich schnell ändernden Zustand so weit unten wie möglich, idealerweise in Modifier-Lambdas?
  • Haben Lazy-Listen stabile Schlüssel?
  • Stecken aufwendige Berechnungen hinter remember / derivedStateOf?
  • Hat der Layout Inspector bestätigt, dass die Anzahl tatsächlich gesunken ist?

Jedes abgehakte Kästchen entfernt in jedem Frame unnötige Arbeit.

Kurztest

Sie übergeben einem Composable eine List<User> und stellen fest, dass es auch dann erneut ausgeführt wird, wenn die Daten unverändert sind. Was ist die direkteste Lösung?

Zusammenfassung: Kleinere, seltenere Recompositions

Sie haben gelernt, Recompositions zu bändigen, das wichtigste Compose-Performance-Problem:

  • Messen Sie die Anzahl mit dem Layout Inspector oder einem Debug-Zähler.
  • Lesen Sie den Zustand so weit unten wie möglich; verzögern Sie Lesevorgänge mit Lambdas.
  • Machen Sie Parameter mit unveränderlichen Daten und @Immutable/ImmutableList stabil.
  • Verwenden Sie remember und derivedStateOf, um Neuberechnungen in jedem Frame zu vermeiden.
  • Geben Sie Lazy-Elementen stabile Schlüssel und halten Sie schnelle Zustandslesevorgänge in Modifier-Lambdas.

Als Nächstes wechseln wir von CPU und Recompositions zum Speicher: Leaks finden und beheben.

Häufig gestellte Fragen

Ist die Lektion „Recomposition bändigen“ kostenlos?

Ja — der vollständige Text von „Recomposition bändigen“ 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 „Recomposition bändigen“?

Stabile Parameter und weniger Recompositions 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 2 von 4.

Wie lange dauert die Lektion „Recomposition bändigen“?

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