Android Coden
Android 6 min lesen

Compose Performance verstehen

Du lernst, warum Compose Performance von Recomposition, Stabilität und Lazy Layouts abhängt. Mit Praxisregel.

Compose Performance beschreibt, wie gut deine Jetpack-Compose-Oberfläche auf Eingaben, Scrollen, Zustandsänderungen und neue Daten reagiert. Für dich als Android-Entwickler ist das wichtig, weil eine App nicht nur korrekt aussehen muss, sondern sich auch zuverlässig flüssig anfühlen soll. Die zentralen Begriffe sind Recomposition, Stability und Lazy Layouts: Sie entscheiden oft darüber, ob eine Compose-App sauber läuft oder bei realen Datenmengen ruckelt.

Was ist das?

Compose Performance bedeutet: Du schreibst Composables so, dass Compose möglichst wenig unnötige Arbeit erledigen muss. Compose zeichnet nicht bei jeder Änderung blind den gesamten Bildschirm neu. Stattdessen beobachtet das Framework State, erkennt betroffene UI-Bereiche und führt dort Recomposition aus. Recomposition heißt, dass Compose bestimmte Composable-Funktionen erneut auswertet, weil sich gelesener State verändert hat.

Das mentale Modell ist wichtig: Ein Composable beschreibt UI für einen aktuellen Zustand. Es ist keine klassische View, die du manuell mutierst. Wenn sich State ändert, ruft Compose die betroffenen Beschreibungen erneut auf und entscheidet danach, welche Änderungen wirklich in die Oberfläche übernommen werden. Performance entsteht also nicht dadurch, dass du Recomposition grundsätzlich verhinderst. Sie entsteht dadurch, dass Recomposition klein, vorhersehbar und billig bleibt.

Stability ist dabei ein Signal an Compose. Wenn Parameter stabil sind, kann Compose besser einschätzen, ob ein Composable übersprungen werden darf. Ein stabiler Wert ändert sich nicht überraschend außerhalb der bekannten State-Regeln. Typische Kandidaten sind unveränderliche Datenklassen mit val-Properties oder primitive Werte. Instabile Parameter können dazu führen, dass Compose häufiger neu auswertet, auch wenn sich aus Nutzersicht nichts Relevantes geändert hat.

Lazy Layouts wie LazyColumn, LazyRow oder Grids sind der zweite große Bereich. Sie rendern nur die sichtbaren Elemente und einige zusätzliche Einträge in der Nähe des Viewports. Damit sind sie die richtige Wahl für Listen mit vielen Elementen. Trotzdem sind sie kein Freifahrtschein: Ohne stabile Keys, ohne sinnvolle Item-Struktur und mit teuren Berechnungen im Item-Content kann auch eine Lazy-Liste langsam werden.

Wie funktioniert es?

Compose arbeitet deklarativ. Du beschreibst, was auf dem Bildschirm stehen soll, und Compose verwaltet die Aktualisierung. Der wichtigste Auslöser ist gelesener State. Wenn ein Composable einen Wert aus State, mutableStateOf, collectAsStateWithLifecycle oder einem ähnlichen Mechanismus liest, registriert Compose diese Abhängigkeit. Ändert sich der Wert, wird der betroffene Bereich für Recomposition eingeplant.

Dabei gilt eine praktische Regel: State sollte so nah wie sinnvoll an der UI liegen, die ihn wirklich braucht, aber nicht tiefer als nötig. Wenn du einen großen Screen-State an sehr viele untergeordnete Composables weitergibst, können schon kleine Änderungen breite Recomposition auslösen. Wenn du dagegen gezielt nur die Werte übergibst, die ein Kind-Composable benötigt, kann Compose genauer arbeiten.

Stability hilft Compose beim Überspringen. Wenn ein Composable mit denselben stabilen Parametern erneut erreicht wird, kann Compose die Ausführung überspringen. Das ist besonders relevant in Listen, Toolbars, Karten und wiederverwendbaren UI-Bausteinen. Du solltest Datenmodelle für UI-Zustand möglichst unveränderlich gestalten. Nutze val, klare Datenklassen und ersetze Listen nur dann, wenn sich der Inhalt fachlich geändert hat. Vermeide veränderliche Listen, die intern geändert werden, ohne dass Compose sauber erkennt, was passiert ist.

remember ist ein weiterer Baustein. Damit speicherst du berechnete Werte über Recompositions hinweg. Das ist sinnvoll für lokale UI-Zustände oder abgeleitete Objekte, deren Erstellung nicht bei jedem Durchlauf nötig ist. Für Werte, die aus anderem State berechnet werden, kann derivedStateOf sinnvoll sein, wenn die Berechnung häufig gelesen wird und sich das Ergebnis seltener ändert als die Eingaben. Du solltest diese Werkzeuge aber nicht reflexartig überall einsetzen. Einfache String-Formatierungen oder kleine Bedingungen brauchen meistens keine zusätzliche Struktur.

Lazy Layouts funktionieren über eine Item-API. Compose verwaltet, welche Items sichtbar sind, und recycelt nicht wie klassische RecyclerViews mit ViewHoldern, sondern komponiert sichtbare Inhalte passend zum Zustand. Für Performance sind stabile Keys wichtig. Ein Key sagt Compose, welches fachliche Element hinter einem Listeneintrag steht. Ohne Key orientiert sich Compose oft an der Position. Wenn du dann Elemente einfügst, entfernst oder sortierst, kann unnötige Arbeit entstehen und lokaler Item-State an der falschen Position landen.

In der Praxis

Stell dir eine Nachrichtenliste vor. Jede Nachricht hat eine ID, einen Autor, einen Text und einen gelesenen Status. Eine typische Compose-Struktur trennt den Screen von den einzelnen Items. Der Screen sammelt State aus dem ViewModel, die Liste erhält die Daten, und jedes Item bekommt nur die Werte, die es wirklich darstellen muss.

data class MessageUi(
    val id: String,
    val author: String,
    val text: String,
    val isRead: Boolean
)

@Composable
fun MessageList(
    messages: List<MessageUi>,
    onMessageClick: (String) -> Unit
) {
    LazyColumn {
        items(
            items = messages,
            key = { message -> message.id }
        ) { message ->
            MessageRow(
                author = message.author,
                text = message.text,
                isRead = message.isRead,
                onClick = { onMessageClick(message.id) }
            )
        }
    }
}

@Composable
fun MessageRow(
    author: String,
    text: String,
    isRead: Boolean,
    onClick: () -> Unit
) {
    val titleStyle = if (isRead) {
        MaterialTheme.typography.bodyLarge
    } else {
        MaterialTheme.typography.titleMedium
    }

    ListItem(
        headlineContent = { Text(author, style = titleStyle) },
        supportingContent = { Text(text, maxLines = 2) },
        modifier = Modifier.clickable(onClick = onClick)
    )
}

An diesem Beispiel siehst du drei Performance-Regeln. Erstens: MessageUi ist unveränderlich. Alle Properties sind val, dadurch ist das Modell gut nachvollziehbar und Compose kann besser mit gleichen Werten umgehen. Zweitens: LazyColumn verwendet key = { message.id }. Wenn eine neue Nachricht oben eingefügt wird, bleiben vorhandene Items fachlich wiedererkennbar. Drittens: MessageRow bekommt keine komplette Screen-Struktur, sondern nur die Werte, die sie braucht.

Eine häufige Stolperfalle ist teure Arbeit direkt im Composable oder im Lazy-Item. Wenn du zum Beispiel bei jedem Item während der Composition ein Datum aufwendig formatierst, Bilder synchron vorbereitest oder große Listen filterst, bezahlst du diese Kosten während UI-Arbeit. Verschiebe solche Arbeit in das ViewModel, in eine Mapper-Schicht oder nutze gezieltes Caching mit remember, wenn die Berechnung wirklich UI-nah ist.

Eine zweite Stolperfalle ist instabiler Callback- oder Objektaufbau in großen Listen. Nicht jeder Lambda-Ausdruck ist automatisch ein Problem, aber wenn du pro Item komplexe Objekte erzeugst, die bei jeder Recomposition neu entstehen, erhöhst du die Arbeit. Prüfe dann, ob du Parameter vereinfachen, Daten vorher vorbereiten oder unnötige Wrapper entfernen kannst. Der wichtigste Maßstab bleibt: Mache erst messbar, wo es ruckelt, und optimiere dann gezielt.

Für den Alltag kannst du dir diese Entscheidungsregel merken: Wenn ein Composable oft sichtbar ist oder in einer Liste viele Male vorkommt, muss sein Input klein, stabil und billig zu verarbeiten sein. Große Screen-Composables dürfen koordinieren. Kleine Item-Composables sollten möglichst nur darstellen.

Validieren kannst du dein Verständnis auf mehreren Ebenen. Im Debugging kannst du Recomposition-Zähler oder Layout Inspector nutzen, um auffällige Bereiche zu finden. In Code-Reviews achtest du auf veränderliche UI-Modelle, fehlende Lazy-Keys, große State-Objekte in kleinen Composables und Berechnungen direkt in der Composition. Mit Tests prüfst du nicht jede Frame-Zeit, aber du sicherst Zustandslogik, Mapper und UI-Verhalten ab. In einer CI-Pipeline helfen diese Tests dabei, Performance-nahe Fehler früher zu erkennen, etwa wenn ein Refactoring plötzlich deutlich mehr UI-Zustand durch den Baum schiebt.

Fazit

Compose Performance ist kein einzelner Trick, sondern eine saubere Arbeitsweise: Du hältst Recomposition klein, modellierst UI-State stabil und behandelst Lazy Layouts als leistungsfähige, aber bewusst zu nutzende Werkzeuge. Nimm dir eine bestehende Compose-Liste aus deinem Projekt, prüfe ihre Keys, die Stabilität der Item-Modelle und mögliche Berechnungen im Item-Content. Danach beobachte mit Debugging- oder Profiling-Werkzeugen, ob deine Annahmen stimmen, und lass genau diese Punkte im nächsten Code-Review gezielt prüfen.

Quellen (6)
Redaktion

Geschrieben von

Redaktion

Das Redaktionsteam recherchiert und schreibt Artikel zu aktuellen Themen rund um Tech, Lifestyle und Ratgeber.