Es können häufige Fehler bei der Verwendung von Compose auftreten. Diese Fehler führen möglicherweise zu Code, der scheinbar gut genug funktioniert, aber die Leistung der Benutzeroberfläche beeinträchtigen kann. Folgen Sie den Best Practices, um Ihre App in Compose zu optimieren.
remember verwenden, um kostenintensive Berechnungen zu minimieren
Composable-Funktionen können sehr häufig ausgeführt werden, manchmal sogar für jeden Frame einer Animation. Aus diesem Grund sollten Sie so wenige Berechnungen wie möglich im Body Ihrer Composable-Funktion durchführen.
Eine wichtige Technik besteht darin, die Ergebnisse von Berechnungen mit
remember zu speichern. Auf diese Weise wird die Berechnung nur einmal ausgeführt und Sie können die Ergebnisse abrufen, wenn sie benötigt werden.
Hier ist beispielsweise Code, der eine sortierte Liste von Namen anzeigt, die Sortierung aber auf sehr kostenintensive Weise durchführt:
@Composable fun ContactList( contacts: List<Contact>, comparator: Comparator<Contact>, modifier: Modifier = Modifier ) { LazyColumn(modifier) { // DON’T DO THIS items(contacts.sortedWith(comparator)) { contact -> // ... } } }
Jedes Mal, wenn ContactsList neu zusammengesetzt wird, wird die gesamte Kontaktliste neu sortiert, obwohl sie sich nicht geändert hat. Wenn der Nutzer durch die Liste scrollt, wird die Composable-Funktion neu zusammengesetzt, sobald eine neue Zeile angezeigt wird.
Um dieses Problem zu beheben, sortieren Sie die Liste außerhalb von LazyColumn und speichern Sie die sortierte Liste mit remember:
@Composable fun ContactList( contacts: List<Contact>, comparator: Comparator<Contact>, modifier: Modifier = Modifier ) { val sortedContacts = remember(contacts, comparator) { contacts.sortedWith(comparator) } LazyColumn(modifier) { items(sortedContacts) { // ... } } }
Die Liste wird jetzt einmal sortiert, wenn ContactList zum ersten Mal zusammengesetzt wird. Wenn sich die Kontakte oder der Comparator ändern, wird die sortierte Liste neu generiert. Andernfalls kann die Composable-Funktion die im Cache gespeicherte sortierte Liste weiter verwenden.
Lazy-Layout-Schlüssel verwenden
Lazy-Layouts verwenden Elemente effizient wieder und generieren oder setzen sie nur dann neu zusammen, wenn es erforderlich ist. Sie können jedoch dazu beitragen, Lazy-Layouts für die Neuzusammensetzung zu optimieren.
Angenommen, eine Nutzeraktion führt dazu, dass ein Element in der Liste verschoben wird. Angenommen, Sie zeigen eine Liste von Notizen an, die nach Änderungszeit sortiert sind, wobei die zuletzt geänderte Notiz oben steht.
@Composable fun NotesList(notes: List<Note>) { LazyColumn { items( items = notes ) { note -> NoteRow(note) } } }
Es gibt jedoch ein Problem mit diesem Code. Angenommen, die unterste Community-Anmerkung wird geändert. Sie ist jetzt die zuletzt geänderte Notiz und wird daher oben in der Liste angezeigt. Alle anderen Notizen werden um eine Position nach unten verschoben.
Ohne Ihre Hilfe erkennt Compose nicht, dass unveränderte Elemente nur in der Liste verschoben werden. Stattdessen geht Compose davon aus, dass das alte Element 2 gelöscht und ein neues für Element 3, Element 4 usw. erstellt wurde. Das Ergebnis ist, dass Compose jedes Element in der Liste neu zusammensetzt, obwohl sich nur eines davon tatsächlich geändert hat.
Die Lösung besteht darin, Element-Schlüssel anzugeben. Wenn Sie für jedes Element einen stabilen Schlüssel angeben, kann Compose unnötige Neuzusammensetzungen vermeiden. In diesem Fall kann Compose feststellen, dass das Element, das sich jetzt an Position 3 befindet, dasselbe ist, das sich zuvor an Position 2 befand. Da sich keine Daten für dieses Element geändert haben, muss Compose es nicht neu zusammensetzen.
@Composable fun NotesList(notes: List<Note>) { LazyColumn { items( items = notes, key = { note -> // Return a stable, unique key for the note note.id } ) { note -> NoteRow(note) } } }
derivedStateOf verwenden, um Neuzusammensetzungen zu begrenzen
Ein Risiko bei der Verwendung von Status in Ihren Kompositionen besteht darin, dass die Benutzeroberfläche bei schnellen Statusänderungen möglicherweise häufiger neu zusammengesetzt wird als erforderlich. Angenommen, Sie zeigen eine scrollbare Liste an. Sie prüfen den Status der Liste, um zu sehen, welches Element das erste sichtbare Element in der Liste ist:
val listState = rememberLazyListState() LazyColumn(state = listState) { // ... } val showButton = listState.firstVisibleItemIndex > 0 AnimatedVisibility(visible = showButton) { ScrollToTopButton() }
Das Problem hierbei ist, dass sich listState ständig ändert, wenn der Nutzer durch die Liste scrollt. Das bedeutet, dass die Liste ständig neu zusammengesetzt wird. Es ist jedoch nicht erforderlich, sie so oft neu zusammenzusetzen. Sie muss erst neu zusammengesetzt werden, wenn unten ein neues Element sichtbar wird. Das führt zu vielen zusätzlichen Berechnungen, was die Leistung der Benutzeroberfläche beeinträchtigt.
Die Lösung besteht darin, abgeleiteten Status zu verwenden. Mit abgeleitetem Status können Sie Compose mitteilen, welche Statusänderungen tatsächlich eine Neuzusammensetzung auslösen sollen. In diesem Fall geben Sie an, dass Sie wissen möchten, wann sich das erste sichtbare Element ändert. Wenn sich dieser Statuswert ändert, muss die Benutzeroberfläche neu zusammengesetzt werden. Wenn der Nutzer jedoch noch nicht weit genug gescrollt hat, um ein neues Element nach oben zu bringen, ist keine Neuzusammensetzung erforderlich.
val listState = rememberLazyListState() LazyColumn(state = listState) { // ... } val showButton by remember { derivedStateOf { listState.firstVisibleItemIndex > 0 } } AnimatedVisibility(visible = showButton) { ScrollToTopButton() }
Lesevorgänge so lange wie möglich verzögern
Wenn ein Leistungsproblem festgestellt wurde, kann es hilfreich sein, Statuslesevorgänge zu verzögern. Das Zurückstellen von Zustandslesevorgängen stellt sicher, dass Compose bei der Neukomposition den minimal möglichen Code noch einmal ausführt. Wenn Ihre Benutzeroberfläche beispielsweise einen Status hat, der im Composable-Baum weit oben ist, und Sie den Status in einer untergeordneten Composable-Funktion lesen, können Sie den Statuslesevorgang in eine Lambda-Funktion einfügen. Dadurch wird der Lesevorgang nur ausgeführt, wenn er tatsächlich erforderlich ist. Eine Referenz finden Sie in der Implementierung in der Jetsnack Beispiel-App. Jetsnack implementiert auf dem Detailbildschirm einen Effekt, der einer reduzierbaren Symbolleiste ähnelt. Warum diese Technik funktioniert, erfahren Sie im Blogpost Jetpack Compose: Debugging Recomposition.
Um diesen Effekt zu erzielen, benötigt die Composable-Funktion Title den Scroll-Offset, um sich mit einem Modifier zu versetzen. Hier ist eine vereinfachte Version des Jetsnack-Codes vor der Optimierung:
@Composable fun SnackDetail() { // ... Box(Modifier.fillMaxSize()) { // Recomposition Scope Start val scroll = rememberScrollState(0) // ... Title(snack, scroll.value) // ... } // Recomposition Scope End } @Composable private fun Title(snack: Snack, scroll: Int) { // ... val offset = with(LocalDensity.current) { scroll.toDp() } Column( modifier = Modifier .offset(y = offset) ) { // ... } }
Wenn sich der Scrollstatus ändert, macht Compose den nächstgelegenen übergeordneten Neuzusammensetzungsbereich ungültig. In diesem Fall ist der nächstgelegene Bereich die Composable-Funktion SnackDetail. Beachten Sie, dass Box eine Inline-Funktion und daher kein Neuzusammensetzungsbereich ist. Compose setzt also SnackDetail und alle Composables-Funktionen in SnackDetail neu zusammen. Wenn Sie Ihren Code so ändern, dass der Status nur dort gelesen wird, wo er tatsächlich verwendet wird, können Sie die Anzahl der Elemente reduzieren, die neu zusammengesetzt werden müssen.
@Composable fun SnackDetail() { // ... Box(Modifier.fillMaxSize()) { // Recomposition Scope Start val scroll = rememberScrollState(0) // ... Title(snack) { scroll.value } // ... } // Recomposition Scope End } @Composable private fun Title(snack: Snack, scrollProvider: () -> Int) { // ... val offset = with(LocalDensity.current) { scrollProvider().toDp() } Column( modifier = Modifier .offset(y = offset) ) { // ... } }
Der Scrollparameter ist jetzt eine Lambda-Funktion. Das bedeutet, dass Title weiterhin auf den erhöhten Status verweisen kann, der Wert aber nur in Title gelesen wird, wo er tatsächlich benötigt wird. Wenn sich der Scrollwert ändert, ist der nächstgelegene Neukompositionsbereich jetzt die komponierbare Funktion Title. Compose muss nicht mehr die gesamte Box neu zusammensetzen.
Das ist eine gute Verbesserung, aber es geht noch besser. Sie sollten misstrauisch sein, wenn Sie eine Neukomposition verursachen, nur um eine komponierbare Funktion neu zu layouten oder neu zu zeichnen. In diesem Fall ändern Sie nur den Offset der Composable-Funktion Title, was in der Layoutphase erfolgen könnte.
@Composable private fun Title(snack: Snack, scrollProvider: () -> Int) { // ... Column( modifier = Modifier .offset { IntOffset(x = 0, y = scrollProvider()) } ) { // ... } }
Bisher wurde im Code Modifier.offset(x: Dp, y: Dp) verwendet, wobei der
Offset als Parameter angegeben wird. Wenn Sie zur Lambda-Version des Modifiers wechseln,
können Sie dafür sorgen, dass die Funktion den Scrollstatus in der Layoutphase liest. Wenn sich der Scrollstatus ändert, kann Compose die Kompositionsphase vollständig überspringen und direkt zur Layoutphase übergehen. Wenn Sie häufig geänderte Statusvariablen an Modifier übergeben, sollten Sie nach Möglichkeit die Lambda-Versionen der Modifier verwenden.
Hier ist ein weiteres Beispiel für diesen Ansatz. Dieser Code wurde noch nicht optimiert:
// Here, assume animateColorBetween() is a function that swaps between // two colors val color by animateColorBetween(Color.Cyan, Color.Magenta) Box( Modifier .fillMaxSize() .background(color) )
Hier wechselt die Hintergrundfarbe des Felds schnell zwischen zwei Farben. Dieser Status ändert sich also sehr häufig. Die Composable-Funktion liest diesen Status dann im Hintergrund-Modifier. Da sich die Farbe in jedem Frame ändert, muss das Feld in jedem Frame neu zusammengesetzt werden.
Um dies zu verbessern, verwenden Sie einen Lambda-basierten Modifier, in diesem Fall drawBehind.
Das bedeutet, dass der Farbstatus nur während der Zeichenphase gelesen wird. Compose kann die Kompositions- und Layoutphasen vollständig überspringen. Wenn sich die Farbe ändert, geht Compose direkt zur Zeichenphase über.
val color by animateColorBetween(Color.Cyan, Color.Magenta) Box( Modifier .fillMaxSize() .drawBehind { drawRect(color) } )
Rückwärtige Schreibvorgänge vermeiden
Compose geht davon aus, dass Sie niemals in einen Status schreiben, der bereits gelesen wurde. Wenn Sie dies tun, wird dies als rückwärtiger Schreibvorgang bezeichnet und kann dazu führen, dass die Neukomposition in jedem Frame endlos erfolgt.
Die folgende Composable-Funktion zeigt ein Beispiel für diese Art von Fehler.
@Composable fun BadComposable() { var count by remember { mutableIntStateOf(0) } // Causes recomposition on click Button(onClick = { count++ }, Modifier.wrapContentSize()) { Text("Recompose") } Text("$count") count++ // Backwards write, writing to state after it has been read</b> }
Dieser Code aktualisiert die Anzahl am Ende der Composable-Funktion, nachdem sie in der vorherigen Zeile gelesen wurde. Wenn Sie diesen Code ausführen, sehen Sie, dass der Zähler nach dem Klicken auf die Schaltfläche, wodurch eine Neukomposition ausgelöst wird, in einer Endlosschleife schnell ansteigt, da Compose diese komponierbare Funktion neu zusammensetzt, einen veralteten Statuslesevorgang feststellt und daher eine weitere Neukomposition plant.
Sie können rückwärtige Schreibvorgänge ganz vermeiden, indem Sie niemals in den Status in der Komposition schreiben. Schreiben Sie nach Möglichkeit immer in den Status als Reaktion auf ein Ereignis und in einer Lambda-Funktion wie im vorherigen onClick-Beispiel.
Zusätzliche Ressourcen
- Leitfaden zur App-Leistung: Best Practices, Bibliotheken und Tools zur Verbesserung der Leistung auf Android.
- Leistung prüfen: App-Leistung prüfen.
- Benchmarking: App-Leistung benchmarken.
- App-Start: App-Start optimieren.
- Baseline-Profile: Informationen zu Baseline-Profilen.
Empfehlungen für Sie
- Hinweis: Linktext wird angezeigt, wenn JavaScript deaktiviert ist
- Status und Jetpack Compose
- Grafik-Modifier
- In Compose denken