Denken in Textform

Jetpack Compose ist ein modernes deklaratives UI-Toolkit für Android. E-Mail schreiben Es vereinfacht das Schreiben und Verwalten der App-UI durch die Bereitstellung einer deklarativen API mit dem Sie die Benutzeroberfläche Ihrer Anwendung rendern können, ohne das Frontend zwingend ändern zu müssen Aufrufe. Diese Terminologie muss erklärt werden, aber die Auswirkungen sind die für Ihr App-Design wichtig sind.

Das deklarative Programmierparadigma

In der Vergangenheit war eine Android-Ansichtshierarchie als Struktur der Benutzeroberfläche dargestellt. Widgets. Wenn sich der Status der App ändert, z. B. aufgrund von Faktoren wie Interaktionen ist, muss die UI-Hierarchie aktualisiert werden, damit die aktuellen Daten angezeigt werden. Die gängigste Methode zur Aktualisierung der Benutzeroberfläche besteht darin, die Baumstruktur mit Funktionen wie findViewById() und Knoten ändern, indem Sie Methoden wie button.setText(String) aufrufen, container.addChild(View) oder img.setImageBitmap(Bitmap). Diese Methoden den internen Status des Widgets zu ändern.

Wenn Sie Ansichten manuell bearbeiten, steigt die Fehlerwahrscheinlichkeit. Wenn ein Datenelement kann es leicht passieren, dass Sie vergessen, eine der Ansichten, in denen sie angezeigt wird, zu aktualisieren. Es ist auch einfach, illegale Staaten zu erstellen, wenn zwei Updates in einem auf unerwartete Weise. Bei einer Aktualisierung könnte z. B. versucht werden, den Wert eines Knotens festzulegen, aus der Benutzeroberfläche entfernt. Im Allgemeinen ist die Komplexität der Softwarewartung steigt mit der Anzahl der Aufrufe, die aktualisiert werden müssen.

In den letzten Jahren hat sich die gesamte Branche deklarativen UI-Modells, das die Entwicklung Erstellung und Aktualisierung von Benutzeroberflächen. Bei der Technik wird konzeptionell den gesamten Bildschirm von Grund auf neu generieren und dann nur Änderungen. Bei diesem Ansatz entfällt die Komplexität der manuellen Aktualisierung einer zustandsorientierten Hierarchieansicht. Compose ist ein deklaratives UI-Framework.

Eine Herausforderung bei der Neuerstellung des gesamten Bildschirms besteht darin, in Bezug auf Zeit, Rechenleistung und Akkunutzung sehr teuer ist. Um das Risiko zu minimieren wählt Compose intelligent aus, welche Teile der Benutzeroberfläche jederzeit erneut gezeichnet werden. Dies hat einige Auswirkungen auf die Art und Weise, Ihre UI-Komponenten, wie unter Neuzusammensetzung erläutert.

Eine einfache zusammensetzbare Funktion

Mithilfe von Compose können Sie Ihre Benutzeroberfläche erstellen, indem Sie composable-Funktionen, die Daten aufnehmen und UI-Elemente ausgeben. Ein einfaches Beispiel ist ein Greeting-Widget, das ein String annimmt und ein Text-Widget ausgibt der eine Begrüßungsnachricht anzeigt.

Screenshot von einem Smartphone mit dem Text

Abbildung 1: Einfache zusammensetzbare Funktion, bei der Daten übergeben werden ein Text-Widget auf dem Bildschirm rendern.

Einige Hinweise zu dieser Funktion:

  • Die Funktion wird mit der Annotation @Composable gekennzeichnet. Alle zusammensetzbaren Funktionen Funktionen müssen diese Annotation enthalten. Diese Anmerkung fließt in das Schreiben Compiler, dass diese Funktion Daten in eine UI konvertieren soll.

  • Die Funktion übernimmt Daten. Zusammensetzbare Funktionen können Parameter akzeptieren, mit denen die App-Logik die Benutzeroberfläche beschreiben kann. In diesem Fall zeigt unser Widget akzeptiert String, sodass der Nutzer mit Namen begrüßt werden kann.

  • Die Funktion zeigt Text auf der Benutzeroberfläche an. Dazu wird die Methode Text() aufgerufen zusammensetzbaren Funktion, die das Text-UI-Element erstellt. Zusammensetzbar -Funktionen geben Benutzeroberflächenhierarchie aus, indem sie andere zusammensetzbare Funktionen aufrufen.

  • Die Funktion gibt nichts zurück. Zusammensetzen von Funktionen, die UI ausgeben alles zurückgeben, weil sie den gewünschten Bildschirmstatus beschreiben. anstatt UI-Widgets zu erstellen.

  • Diese Funktion ist schnell, idempotent, und frei von Nebeneffekten.

    • Die Funktion verhält sich gleich, wenn sie mehrfach mit dem Parameter Argument und verwendet keine anderen Werte wie oder random()-Aufrufe.
    • Die Funktion beschreibt die Benutzeroberfläche ohne Nebeneffekte wie oder globale Variablen ändern.

    Im Allgemeinen sollten alle zusammensetzbaren Funktionen so geschrieben werden: aus Gründen, die unter Neuzusammensetzung beschrieben werden.

Der deklarative Paradigmenwechsel

Bei vielen imperativen objektorientierten UI-Toolkits initialisieren Sie die UI, indem Sie eine Struktur von Widgets instanziieren. Sie tun dies oft, indem Sie ein XML-Layout erweitern, -Datei. Jedes Widget behält seinen eigenen internen Status und Setter-Methoden, die es der App-Logik ermöglichen, mit dem Widget zu interagieren.

Beim deklarativen Ansatz von Compose sind Widgets relativ zustandslos und legen keine Setter- oder Getter-Funktionen offen. Tatsächlich Widgets werden nicht als Objekte angezeigt. Sie aktualisieren die Benutzeroberfläche, indem Sie die Methode Funktion mit unterschiedlichen Argumenten. So können Sie ganz einfach Architekturmustern wie einem ViewModel, wie in den Leitfaden zur Anwendungsarchitektur. Dann sind Ihre zusammensetzbaren Funktionen ist dafür verantwortlich, den aktuellen Anwendungsstatus jedes Mal in eine UI umzuwandeln. Aktualisierungen der beobachtbaren Daten.

Darstellung des Datenflusses in einer Compose-Benutzeroberfläche, von übergeordneten Objekten bis zu ihren untergeordneten Elementen.

Abbildung 2: Die App-Logik stellt der zusammensetzbaren Funktion der obersten Ebene Daten bereit. Diese Funktion verwendet die Daten, um die Benutzeroberfläche zu beschreiben, indem sie andere zusammensetzbare Funktionen aufruft. übergibt die entsprechenden Daten an diese zusammensetzbaren Funktionen und weiter nach unten in der Hierarchie.

Wenn der Nutzer mit der UI interagiert, löst sie Ereignisse wie onClick aus. Diese Ereignisse sollten die Anwendungslogik benachrichtigen, die dann den Status der Anwendung ändern kann. Wenn sich der Status ändert, werden die zusammensetzbaren Funktionen erneut mit den neuen Daten. Dies führt dazu, dass die UI-Elemente neu gezeichnet werden. Dieser Vorgang wird als recomposition um.

Darstellung, wie UI-Elemente auf Interaktionen reagieren, indem sie Ereignisse auslösen, die von der App-Logik verarbeitet werden

Abbildung 3: Der Nutzer hat mit einem UI-Element interagiert, wodurch ein Ereignis ausgelöst. Die App-Logik reagiert auf das Ereignis, dann werden die zusammensetzbaren Funktionen werden bei Bedarf automatisch mit neuen Parametern aufgerufen.

Dynamischer Content

Da zusammensetzbare Funktionen in Kotlin und nicht in XML geschrieben werden, können sie so dynamisch wie jeder andere Kotlin-Code. Angenommen, Sie möchten eine UI erstellen, die eine Liste von Nutzenden begrüßt:

@Composable
fun Greeting(names: List<String>) {
    for (name in names) {
        Text("Hello $name")
    }
}

Diese Funktion verwendet eine Liste von Namen und generiert eine Begrüßung für jeden Nutzer. Zusammensetzbare Funktionen können sehr komplex sein. Mit if-Anweisungen können Sie ob ein bestimmtes UI-Element angezeigt werden soll oder nicht. Sie können Schleifen verwenden. Sie können Hilfsfunktionen aufrufen. Sie haben die volle Flexibilität der zugrunde liegenden Sprache. Diese Leistung und Flexibilität sind einer der wichtigsten Vorteile von Jetpack Schreiben.

Neuzusammensetzung

In einem imperativen UI-Modell rufen Sie zum Ändern eines Widgets einen Setter im Widget auf seinen internen Status zu ändern. In Compose rufen Sie die zusammensetzbare Funktion erneut auf mit neuen Daten. Dies führt zu einer Neuzusammensetzung der Funktion, d. h. den Widgets. die von der Funktion ausgegeben wurden, werden bei Bedarf mit neuen Daten neu gezeichnet. Die E-Mail-Adresse „Compose“ kann nur die geänderten Komponenten intelligent neu zusammensetzen.

Sehen Sie sich zum Beispiel die folgende zusammensetzbare Funktion an, die eine Schaltfläche anzeigt:

@Composable
fun ClickCounter(clicks: Int, onClick: () -> Unit) {
    Button(onClick = onClick) {
        Text("I've been clicked $clicks times")
    }
}

Bei jedem Klick auf die Schaltfläche aktualisiert der Anrufer den Wert von clicks. Compose ruft die Lambda-Funktion noch einmal mit der Funktion Text auf, um den neuen Wert anzuzeigen. Dieser Vorgang wird als Neuzusammensetzung bezeichnet. Andere Funktionen, die nicht vom werden nicht neu zusammengesetzt.

Wie bereits erwähnt, kann die Neuzusammensetzung des gesamten UI-Baums rechenintensiv sein. was Rechenleistung und Akkulaufzeit beansprucht. „Schreiben“ löst dieses Problem. mit dieser intelligenten Neuzusammensetzung.

Bei der Neuzusammensetzung werden zusammensetzbare Funktionen erneut aufgerufen, Eingaben ändern. Dies geschieht, wenn sich die Eingaben der Funktion ändern. Beim Schreiben auf der Grundlage neuer Eingaben neu zusammensetzt, werden nur die Funktionen oder Lambdas aufgerufen, die sich möglicherweise geändert haben, und der Rest wird übersprungen. Indem alle Funktionen oder Lambdas übersprungen werden die keine geänderten Parameter haben, kann die Funktion "Compose" effizient neu zusammengesetzt werden.

Verlassen Sie sich niemals auf Nebeneffekte bei der Ausführung zusammensetzbarer Funktionen, da ein die Neuzusammensetzung der Funktion übersprungen. Andernfalls kann es bei Nutzern zu ungewöhnlichen unvorhersehbares Verhalten in Ihrer App. Ein Nebeneffekt ist ein Änderung, die für den Rest der App sichtbar ist. Zum Beispiel werden diese Aktionen sind alles gefährliche Nebenwirkungen:

  • In eine Eigenschaft eines gemeinsam genutzten Objekts schreiben
  • Beobachtbares Objekt in ViewModel aktualisieren
  • Gemeinsame Einstellungen werden aktualisiert

Zusammensetzbare Funktionen können genauso oft wie jeden Frame neu ausgeführt werden, z. B. wenn wenn eine Animation gerendert wird. Zusammensetzbare Funktionen sollten schnell sein, um sie zu vermeiden. Verzögerungen bei Animationen. Wenn Sie kostspielige Vorgänge wie das Lesen aus gemeinsamen Einstellungen, in einer Hintergrundkoroutine und übergeben Sie den Wert der zusammensetzbaren Funktion als Parameter zurückgegeben.

Mit diesem Code wird beispielsweise eine zusammensetzbare Funktion erstellt, um einen Wert in SharedPreferences Die zusammensetzbare Funktion darf nicht aus freigegebenen Elementen lesen oder schreiben. Einstellungen selbst. Stattdessen verschiebt dieser Code die Lese- und Schreibvorgänge in einen ViewModel in einer Hintergrundkoroutine. Die Anwendungslogik übergibt den aktuellen Wert mit einem um ein Update auszulösen.

@Composable
fun SharedPrefsToggle(
    text: String,
    value: Boolean,
    onValueChanged: (Boolean) -> Unit
) {
    Row {
        Text(text)
        Checkbox(checked = value, onCheckedChange = onValueChanged)
    }
}

In diesem Dokument werden eine Reihe von Dingen erläutert, die Sie bei der Verwendung von „Compose“ beachten sollten:

  • Zusammensetzbare Funktionen können in beliebiger Reihenfolge ausgeführt werden.
  • Zusammensetzbare Funktionen können parallel ausgeführt werden.
  • Bei der Neuzusammensetzung werden so viele zusammensetzbare Funktionen und Lambdas wie möglich übersprungen.
  • Die Neuzusammensetzung ist optimistisch und kann abgebrochen werden.
  • Eine zusammensetzbare Funktion kann recht häufig ausgeführt werden, so oft wie jeder Frame. einer Animation.

In den folgenden Abschnitten erfahren Sie, wie Sie zusammensetzbare Funktionen erstellen, Neuzusammensetzung. In jedem Fall empfiehlt es sich, die zusammensetzbaren Funktionen funktioniert schnell, idempotent und frei von Nebenwirkungen.

Zusammensetzbare Funktionen können in beliebiger Reihenfolge ausgeführt werden

Wenn Sie sich den Code für eine zusammensetzbare Funktion ansehen, gehen Sie vielleicht davon aus, dass der in der Reihenfolge ausgeführt wird, in der er angezeigt wird. Das stimmt aber nicht unbedingt. Wenn ein die zusammensetzbare Funktion Aufrufe an andere zusammensetzbare Funktionen enthält, können Funktionen in beliebiger Reihenfolge ausgeführt werden. Mit der Funktion „Schreiben“ haben einige UI-Elemente eine höhere Priorität als andere und zeichnen sie zuerst ein.

Angenommen, Sie verwenden Code wie diesen, um drei Bildschirme in einem Tab zu zeichnen. Layout:

@Composable
fun ButtonRow() {
    MyFancyNavigation {
        StartScreen()
        MiddleScreen()
        EndScreen()
    }
}

Die Aufrufe von StartScreen, MiddleScreen und EndScreen können an folgenden Stellen erfolgen: Reihenfolge. Das bedeutet, dass Sie z. B. nicht festlegen können, dass StartScreen() einige globale Einstellungen Variable (ein Nebeneffekt) und lässt MiddleScreen() nutzen, ändern können. Stattdessen muss jede dieser Funktionen für sich stehen.

Zusammensetzbare Funktionen können parallel ausgeführt werden

Mit Compose kann die Neuzusammensetzung optimiert werden, indem zusammensetzbare Funktionen parallel ausgeführt werden. Dadurch kann Compose mehrere Kerne nutzen und zusammensetzbare Funktionen ausführen nicht mit niedrigerer Priorität auf dem Bildschirm.

Diese Optimierung bedeutet, dass eine zusammensetzbare Funktion in einem Pool Hintergrundthreads. Wenn eine zusammensetzbare Funktion eine Funktion in einem ViewModel aufruft, Compose kann diese Funktion möglicherweise von mehreren Threads gleichzeitig aufrufen.

Damit sich Ihre Anwendung korrekt verhält, sollten alle zusammensetzbaren Funktionen keine Nebenwirkungen haben. Lösen Sie stattdessen Nebeneffekte von Callbacks aus, z. B. onClick, die immer im UI-Thread ausgeführt werden.

Wenn eine zusammensetzbare Funktion aufgerufen wird, kann der Aufruf auf einer anderen des Anrufers zu schreiben. Das bedeutet Code, der Variablen in einer zusammensetzbaren Funktion ändert, Lambda sollte vermieden werden – sowohl, weil dieser Code nicht threadsicher ist, es ist ein unzulässiger Nebeneffekt des zusammensetzbaren Lambda.

Das folgende Beispiel zeigt eine zusammensetzbare Funktion, bei der eine Liste und ihre Anzahl angezeigt werden:

@Composable
fun ListComposable(myList: List<String>) {
    Row(horizontalArrangement = Arrangement.SpaceBetween) {
        Column {
            for (item in myList) {
                Text("Item: $item")
            }
        }
        Text("Count: ${myList.size}")
    }
}

Dieser Code ist keine Nebeneffekte und wandelt die Eingabeliste in die UI um. Das ist großartig zum Anzeigen einer kleinen Liste. Wenn die Funktion jedoch in eine lokale ist dieser Code nicht threadsicher oder korrekt:

@Composable
@Deprecated("Example with bug")
fun ListWithBug(myList: List<String>) {
    var items = 0

    Row(horizontalArrangement = Arrangement.SpaceBetween) {
        Column {
            for (item in myList) {
                Text("Item: $item")
                items++ // Avoid! Side-effect of the column recomposing.
            }
        }
        Text("Count: $items")
    }
}

In diesem Beispiel wird items bei jeder Neuzusammensetzung geändert. Das könnte zum Beispiel oder wenn die Liste aktualisiert wird. In beiden Fällen wird die Benutzeroberfläche die falsche Anzahl anzeigen. Aus diesem Grund werden Schreibvorgänge wie diese in Schreiben: indem wir diese Schreibvorgänge verbieten, kann das Framework Threads ändern. um zusammensetzbare Lambdas auszuführen.

Bei der Neuzusammensetzung werden möglichst viele übersprungen

Wenn Teile der Benutzeroberfläche ungültig sind, versucht die Funktion "Compose" nur, die aktualisiert werden müssen. Das bedeutet, dass eine einzelne Schaltfläche kann zusammensetzbar sein, ohne dass eine der darüber oder darunter liegenden zusammensetzbaren Funktionen ausgeführt werden kann. in der Baumstruktur der Benutzeroberfläche.

Jede zusammensetzbare Funktion und Lambda-Funktion kann sich von selbst wieder zusammensetzen. Hier ist ein Beispiel, das zeigt, wie bei einer Neuzusammensetzung einige Elemente übersprungen werden können beim Rendern einer Liste:

/**
 * Display a list of names the user can click with a header
 */
@Composable
fun NamePicker(
    header: String,
    names: List<String>,
    onNameClicked: (String) -> Unit
) {
    Column {
        // this will recompose when [header] changes, but not when [names] changes
        Text(header, style = MaterialTheme.typography.bodyLarge)
        Divider()

        // LazyColumn is the Compose version of a RecyclerView.
        // The lambda passed to items() is similar to a RecyclerView.ViewHolder.
        LazyColumn {
            items(names) { name ->
                // When an item's [name] updates, the adapter for that item
                // will recompose. This will not recompose when [header] changes
                NamePickerItem(name, onNameClicked)
            }
        }
    }
}

/**
 * Display a single name the user can click.
 */
@Composable
private fun NamePickerItem(name: String, onClicked: (String) -> Unit) {
    Text(name, Modifier.clickable(onClick = { onClicked(name) }))
}

Unter Umständen wird bei einer Neuzusammensetzung jeder dieser Bereiche als einzige ausgeführt. Beim Schreiben wird möglicherweise zur Lambda-Funktion „Column“ gewechselt, ohne dass eines der übergeordneten Elemente ausgeführt wird wenn sich header ändert. Beim Ausführen von Column wählt Compose möglicherweise Folgendes aus: die LazyColumn-Elemente überspringen, wenn sich names nicht geändert hat.

Auch hier sollte die Ausführung aller zusammensetzbaren Funktionen oder Lambdas keine Nebenwirkungen haben. Wenn Sie einen Nebeneffekt ausführen müssen, lösen Sie ihn über einen Callback aus.

Die Neuzusammensetzung ist optimistisch

Die Neuzusammensetzung beginnt, wenn Compose annimmt, dass die Parameter einer zusammensetzbaren Funktion sich geändert haben. Die Neuzusammensetzung ist optimistisch, was bedeutet, dass Compose um die Neuzusammensetzung abzuschließen, bevor sich die Parameter wieder ändern. Wenn ein Parameter muss sich ändern, bevor die Neuzusammensetzung abgeschlossen ist, wird durch die Funktion "Compose" neu zusammensetzen und mit dem neuen Parameter neu starten.

Wird die Neuzusammensetzung abgebrochen, wird durch „Compose“ die UI-Baumstruktur aus der Neuzusammensetzung. Wenn es Nebenwirkungen gibt, die davon abhängen, dass die Benutzeroberfläche wird der Nebeneffekt auch dann angewendet, wenn die Komposition abgebrochen wird. Dies kann zu einem inkonsistenten App-Status führen.

Sicherstellen, dass alle zusammensetzbaren Funktionen und Lambdas idempotent und Nebenwirkung sind eine optimistische Neuzusammensetzung.

Zusammensetzbare Funktionen können recht häufig ausgeführt werden.

In einigen Fällen wird für jeden Frame einer Benutzeroberfläche eine zusammensetzbare Funktion ausgeführt. Animation. Wenn die Funktion kostspielige Vorgänge wie das Lesen aus Gerätespeicher verwenden, kann die Funktion zu UI-Verzögerungen führen.

Wenn Ihr Widget beispielsweise versucht hat, Geräteeinstellungen zu lesen, diese Einstellungen hunderte Male pro Sekunde zu lesen, was katastrophale Folgen die Leistung Ihrer App zu verbessern.

Wenn die zusammensetzbare Funktion Daten benötigt, sollten Parameter für die Daten definiert werden. Sie können dann teure Arbeit in einen anderen Thread außerhalb Zusammensetzung und übergeben Sie die Daten mit mutableStateOf oder LiveData an Compose.

Weitere Informationen

Weitere Informationen zu den Funktionen „Schreiben“ und zusammensetzbaren Funktionen finden Sie in die folgenden zusätzlichen Ressourcen.

Videos