Lebenszyklus von zusammensetzbaren Funktionen

Auf dieser Seite erfahren Sie mehr über den Lebenszyklus einer zusammensetzbaren Funktion und wie Compose entscheidet, ob eine zusammensetzbare Funktion neu zusammengesetzt werden muss.

Lebenszyklusübersicht

Wie in der Dokumentation zur Statusverwaltung erwähnt, beschreibt eine Komposition die Benutzeroberfläche Ihrer App und wird durch das Ausführen von zusammensetzbaren Funktionen erstellt. Eine Komposition ist eine Baumstruktur der zusammensetzbaren Funktionen, die Ihre Benutzeroberfläche beschreiben.

Wenn Jetpack Compose Ihre zusammensetzbaren Funktionen zum ersten Mal ausführt, erfasst es bei der ersten Komposition die zusammensetzbaren Funktionen, die Sie aufrufen, um Ihre UI in einer Komposition zu beschreiben. Wenn sich dann der Status der Anwendung ändert, plant Jetpack Composer eine Neuzusammensetzung. Bei der Neuzusammensetzung führt Jetpack Compose die zusammensetzbaren Funktionen, die sich als Reaktion auf Statusänderungen geändert haben, neu aus und aktualisiert dann die Zusammensetzung, um alle Änderungen widerzuspiegeln.

Eine Komposition kann nur durch eine anfängliche Komposition erstellt und durch Neuzusammensetzung aktualisiert werden. Eine Komposition kann nur durch Neuzusammensetzung geändert werden.

Diagramm, das den Lebenszyklus einer zusammensetzbaren Funktion zeigt

Abbildung 1: Lebenszyklus einer zusammensetzbaren Funktion in der Zusammensetzung Er tritt in die Komposition ein, wird null oder öfter neu zusammengesetzt und verlässt die Komposition.

Die Neuzusammensetzung wird normalerweise durch eine Änderung an einem State<T>-Objekt ausgelöst. „Compose“ verfolgt diese und führt alle zusammensetzbaren Funktionen in der Komposition aus, die diese bestimmte State<T> lesen, sowie alle von ihnen aufgerufenen zusammensetzbaren Funktionen, die nicht übersprungen werden können.

Wenn eine zusammensetzbare Funktion mehrmals aufgerufen wird, werden mehrere Instanzen in der Komposition platziert. Jeder Aufruf hat in der Zusammensetzung seinen eigenen Lebenszyklus.

@Composable
fun MyComposable() {
    Column {
        Text("Hello")
        Text("World")
    }
}

Diagramm, das die hierarchische Anordnung der Elemente im vorherigen Code-Snippet zeigt

Abbildung 2: Darstellung von MyComposable in der Komposition. Wenn eine zusammensetzbare Funktion mehrmals aufgerufen wird, werden mehrere Instanzen in der Komposition platziert. Ein Element mit einer anderen Farbe weist darauf hin, dass es eine separate Instanz ist.

Anatomie einer Zusammensetzung in einer Komposition

Die Instanz einer zusammensetzbaren Funktion in der Komposition wird durch die entsprechende Aufrufwebsite identifiziert. Der Compose-Compiler betrachtet jede Aufruf-Website als eigenständige Website. Durch das Aufrufen von Zusammensetzbaren von mehreren Aufrufwebsites werden mehrere Instanzen der zusammensetzbaren Funktion in der Komposition erstellt.

Wenn eine zusammensetzbare Funktion während einer Neuzusammensetzung andere zusammensetzbare Funktionen aufruft als während der vorherigen Zusammensetzung, erkennt Compose, welche Zusammensetzungen aufgerufen wurden oder nicht. Für die zusammensetzbaren Funktionen, die in beiden Kompositionen aufgerufen wurden, vermeidet Compose eine Neuzusammensetzung, wenn sich die Eingaben nicht geändert haben.

Die Bewahrung der Identität ist entscheidend, um Nebenwirkungen mit der zusammensetzbaren Funktion zu verknüpfen, damit sie erfolgreich abgeschlossen werden kann und nicht bei jeder Neuzusammensetzung neu gestartet werden kann.

Betrachten Sie das folgende Beispiel:

@Composable
fun LoginScreen(showError: Boolean) {
    if (showError) {
        LoginError()
    }
    LoginInput() // This call site affects where LoginInput is placed in Composition
}

@Composable
fun LoginInput() { /* ... */ }

@Composable
fun LoginError() { /* ... */ }

Im Code-Snippet oben ruft LoginScreen die zusammensetzbare Funktion LoginError und immer die zusammensetzbare Funktion LoginInput auf. Jeder Aufruf hat eine eindeutige Aufrufwebsite und Quellposition, anhand derer der Compiler ihn eindeutig identifiziert.

Diagramm, das zeigt, wie der vorherige Code neu zusammengesetzt wird, wenn das Flag showError in „true“ geändert wird. Die zusammensetzbare Funktion „LoginError“ wird hinzugefügt, die anderen zusammensetzbaren Funktionen jedoch nicht.

Abbildung 3: Darstellung von LoginScreen in der Komposition, wenn sich der Status ändert und eine Neuzusammensetzung stattfindet. Die gleiche Farbe bedeutet, dass es nicht neu zusammengesetzt wurde.

Obwohl LoginInput statt zuerst zuerst aufgerufen wurde, wird die Instanz LoginInput bei Neuzusammensetzungen beibehalten. Da LoginInput keine Parameter hat, die sich bei der Neuzusammensetzung geändert haben, wird der Aufruf von LoginInput von „Compose“ übersprungen.

Zusätzliche Informationen für eine intelligente Neuzusammensetzung hinzufügen

Wenn eine zusammensetzbare Funktion mehrmals aufgerufen wird, wird sie auch mehrmals zur Komposition hinzugefügt. Wenn eine zusammensetzbare Funktion mehrmals von derselben Aufrufwebsite aufgerufen wird, verfügt Compose nicht über Informationen, um jeden Aufruf dieser zusammensetzbaren Funktion eindeutig zu identifizieren. Daher wird zusätzlich zur Aufrufwebsite die Ausführungsreihenfolge verwendet, um die Instanzen voneinander zu unterscheiden. Manchmal reicht dieses Verhalten aus, es kann aber in einigen Fällen zu unerwünschtem Verhalten führen.

@Composable
fun MoviesScreen(movies: List<Movie>) {
    Column {
        for (movie in movies) {
            // MovieOverview composables are placed in Composition given its
            // index position in the for loop
            MovieOverview(movie)
        }
    }
}

Im obigen Beispiel verwendet Compose zusätzlich zur Aufrufwebsite die Ausführungsreihenfolge, damit die Instanz in der Komposition klar erkennbar bleibt. Wenn am unten der Liste eine neue movie hinzugefügt wird, kann Compose die Instanzen wiederverwenden, die sich bereits in der Komposition befinden, da sich ihre Position in der Liste nicht geändert hat. Daher ist die movie-Eingabe für diese Instanzen dieselbe.

Diagramm, das zeigt, wie der vorherige Code neu zusammengesetzt wird, wenn am Ende der Liste ein neues Element hinzugefügt wird. Die anderen Elemente in der Liste haben ihre Position nicht geändert und werden nicht neu zusammengesetzt.

Abbildung 4: Darstellung von MoviesScreen in der Komposition, wenn am Ende der Liste ein neues Element hinzugefügt wird. Die zusammensetzbaren Funktionen MovieOverview in der Komposition können wiederverwendet werden. Dieselbe Farbe in MovieOverview bedeutet, dass die zusammensetzbare Funktion nicht neu zusammengesetzt wurde.

Wenn sich die movies-Liste jedoch ändert, indem Elemente entweder zum oben oder in der Mitte der Liste hinzugefügt oder Elemente entfernt oder neu angeordnet werden, führt dies zu einer Neuzusammensetzung in allen MovieOverview-Aufrufen, deren Eingabeparameter die Position in der Liste geändert hat. Das ist beispielsweise äußerst wichtig, wenn MovieOverview ein Filmbild mithilfe eines Nebeneffekts abruft. Findet eine Neuzusammensetzung während des Effekts statt, wird er abgebrochen und beginnt von vorn.

@Composable
fun MovieOverview(movie: Movie) {
    Column {
        // Side effect explained later in the docs. If MovieOverview
        // recomposes, while fetching the image is in progress,
        // it is cancelled and restarted.
        val image = loadNetworkImage(movie.url)
        MovieHeader(image)

        /* ... */
    }
}

Diagramm, das zeigt, wie der vorherige Code neu zusammengesetzt wird, wenn ein neues Element an den Anfang der Liste gesetzt wird. Jedes andere Element in der Liste ändert die Position und muss neu zusammengesetzt werden.

Abbildung 5: Darstellung von MoviesScreen in der Komposition, wenn der Liste ein neues Element hinzugefügt wird Zusammensetzbare MovieOverview-Elemente können nicht wiederverwendet werden und alle Nebeneffekte werden neu gestartet. Eine andere Farbe in MovieOverview bedeutet, dass die zusammensetzbare Funktion neu zusammengesetzt wurde.

Im Idealfall ist die Identität der MovieOverview-Instanz mit der Identität der movie verknüpft, die an sie übergeben wird. Wenn wir die Liste der Filme neu anordnen, würden wir idealerweise auch die Instanzen im Kompositionsbaum neu anordnen, anstatt jede zusammensetzbare MovieOverview mit einer anderen Filminstanz neu zusammenzusetzen. Mit der Funktion „Compose“ können Sie der Laufzeit mitteilen, welche Werte zum Identifizieren eines bestimmten Teils der Baumstruktur verwendet werden sollen: der zusammensetzbaren Funktion key.

Durch das Einbinden eines Codeblocks in einen Aufruf an die zusammensetzbare Schlüsselfunktion mit einem oder mehreren übergebenen Werten werden diese Werte kombiniert, um diese Instanz in der Zusammensetzung zu identifizieren. Der Wert für eine key muss nicht global eindeutig sein. Er darf nur unter den Aufrufen von zusammensetzbaren Funktionen auf der Aufrufwebsite eindeutig sein. In diesem Beispiel benötigt jedes movie also ein key, das unter den movies eindeutig ist. Es ist in Ordnung, wenn es dieses key mit einer anderen zusammensetzbaren Funktion an anderer Stelle in der App teilt.

@Composable
fun MoviesScreenWithKey(movies: List<Movie>) {
    Column {
        for (movie in movies) {
            key(movie.id) { // Unique ID for this movie
                MovieOverview(movie)
            }
        }
    }
}

Selbst wenn sich die Elemente auf der Liste ändern, erkennt Compose einzelne Aufrufe von MovieOverview und kann wiederverwendet werden.

Diagramm, das zeigt, wie der vorherige Code neu zusammengesetzt wird, wenn ein neues Element an den Anfang der Liste gesetzt wird. Da die Listenelemente durch Schlüssel gekennzeichnet sind, weiß Compose nicht, dass sie neu zusammengesetzt werden müssen, auch wenn sich ihre Positionen geändert haben.

Abbildung 6: Darstellung von MoviesScreen in der Komposition, wenn der Liste ein neues Element hinzugefügt wird Da die zusammensetzbaren Funktionen von MovieOverview eindeutige Schlüssel haben, erkennt Compose, welche MovieOverview-Instanzen nicht geändert wurden, und kann diese wiederverwenden. Die Nebeneffekte werden weiterhin ausgeführt.

Einige zusammensetzbare Funktionen unterstützen die zusammensetzbare Funktion key. Beispielsweise lässt sich für LazyColumn eine benutzerdefinierte key in items DSL angeben.

@Composable
fun MoviesScreenLazy(movies: List<Movie>) {
    LazyColumn {
        items(movies, key = { movie -> movie.id }) { movie ->
            MovieOverview(movie)
        }
    }
}

Überspringen, wenn sich die Eingaben nicht geändert haben

Bei der Neuzusammensetzung kann die Ausführung einiger zusammensetzbarer Funktionen komplett übersprungen werden, wenn sich ihre Eingaben gegenüber der vorherigen Zusammensetzung nicht geändert haben.

Eine zusammensetzbare Funktion kann übersprungen werden, außer

  • Die Funktion hat einen Rückgabetyp, der nicht Unit ist
  • Die Funktion ist mit @NonRestartableComposable oder @NonSkippableComposable annotiert.
  • Ein erforderlicher Parameter ist ein instabiler Typ.

Es gibt den experimentellen Compiler-Modus Strong Skipping, der die letzte Anforderung lockert.

Damit ein Typ als stabil gilt, muss er folgenden Vertrag einhalten:

  • Das Ergebnis von equals für zwei Instanzen ist immer dasselbe für die beiden Instanzen.
  • Wenn sich ein öffentliches Eigentum dieses Typs ändert, wird die Komposition benachrichtigt.
  • Alle Arten öffentlicher Immobilien sind ebenfalls unverändert.

Es gibt einige wichtige gängige Typen, die unter diesen Vertrag fallen und vom Compiler als stabil behandelt werden, obwohl sie nicht explizit mit der Annotation @Stable als stabil gekennzeichnet sind:

  • Alle primitiven Werttypen: Boolean, Int, Long, Float, Char usw.
  • Strings
  • Alle Funktionstypen (Lambdas)

Alle diese Typen können dem Vertrag der stabilen Version folgen, da sie unveränderlich sind. Da sich unveränderliche Typen niemals ändern, müssen sie die Zusammensetzung nie über die Änderung informieren, sodass es viel einfacher ist, diesen Vertrag einzuhalten.

Ein bemerkenswerter Typ, der stabil ist, aber änderbar ist, ist der MutableState-Typ von Compose. Wenn ein Wert in einem MutableState enthalten ist, gilt das Statusobjekt insgesamt als stabil, da Compose über alle Änderungen an der .value-Eigenschaft von State benachrichtigt wird.

Wenn alle Typen, die als Parameter an eine zusammensetzbare Funktion übergeben werden, stabil sind, werden die Parameterwerte anhand der zusammensetzbaren Position in der UI-Struktur auf Übereinstimmung geprüft. Die Neuzusammensetzung wird übersprungen, wenn alle Werte seit dem vorherigen Aufruf unverändert sind.

Compose betrachtet einen Typ nur dann als stabil, wenn er dies nachweisen kann. Beispielsweise wird eine Schnittstelle im Allgemeinen als nicht stabil behandelt und Typen mit änderbaren öffentlichen Attributen, deren Implementierung unveränderlich sein könnte, sind ebenfalls nicht stabil.

Wenn Compose nicht ableiten kann, dass ein Typ stabil ist, Sie aber erzwingen möchten, dass Composer als stabil behandelt wird, markieren Sie ihn mit der Annotation @Stable.

// Marking the type as stable to favor skipping and smart recompositions.
@Stable
interface UiState<T : Result<T>> {
    val value: T?
    val exception: Throwable?

    val hasError: Boolean
        get() = exception != null
}

Da UiState im obigen Code-Snippet eine Schnittstelle ist, wird dieser Typ in Compose normalerweise als instabil eingestuft. Durch das Hinzufügen der Annotation @Stable teilen Sie Compose mit, dass dieser Typ stabil ist, sodass Compose intelligente Neuzusammensetzungen bevorzugt. Das bedeutet auch, dass Compose alle Implementierungen als stabil behandelt, wenn die Schnittstelle als Parametertyp verwendet wird.