Stabilität in der Funktion „Schreiben“

In Compose werden Typen als stabil oder instabil betrachtet. Ein Typ ist stabil, wenn er unveränderlich ist oder wenn Compose erkennen kann, ob sich sein Wert zwischen den Recomposition-Vorgängen geändert hat. Ein Typ ist instabil, wenn Compose nicht wissen kann, ob sich sein Wert zwischen Recompositionen geändert hat.

Compose verwendet die Stabilität der Parameter einer kombinierbaren Funktion, um zu bestimmen, wie Eingaben verglichen werden und ob die kombinierbare Funktion während der Neukombination übersprungen werden kann (mit dem strikten Überspringmodus, der ab Kotlin 2.0.20 standardmäßig aktiviert ist):

  • Stabile Parameter:Compose vergleicht stabile Parameter mithilfe der strukturellen Gleichheit (Object.equals()). Wenn die stabilen Parameter eines Composables mit ihren vorherigen Werten übereinstimmen, wird es von Compose übersprungen.
  • Instabile Parameter:Wenn der Modus für starkes Überspringen aktiviert ist (die Standardeinstellung in Kotlin 2.0.20 und höher), vergleicht Compose instabile Parameter mit der Instanzgleichheit (===) und überspringt die zusammensetzbare Funktion, wenn dieselben Objektinstanzen übergeben werden. In Versionen vor Kotlin 2.0.20 (oder wenn der Modus „Strong skipping“ deaktiviert ist) wird ein Composable mit instabilen Parametern immer neu zusammengesetzt, wenn sein übergeordnetes Element neu zusammengesetzt wird.

Wenn Ihre App häufig neue Instanzen instabiler Parameter zuweist oder durch eine zu hohe Anzahl von Anmerkungen in Modellen kostspielige .equals()-Prüfungen für große Sammlungen erzwingt, kann es zu unnötigen Recomposition- oder Vergleichsaufwand kommen.

In diesem Dokument wird beschrieben, wie Compose die Stabilität bestimmt und wie Sie sie optimieren können, um die Leistung und die allgemeine Nutzerfreundlichkeit zu verbessern.

Unveränderliche Objekte

Die folgenden Snippets veranschaulichen die allgemeinen Prinzipien hinter Stabilität und Neukomposition.

Die Klasse Contact ist eine unveränderliche Datenklasse. Das liegt daran, dass alle seine Parameter mit dem Schlüsselwort val definierte Primitiven sind. Nachdem Sie eine Instanz von Contact erstellt haben, können Sie den Wert der Eigenschaften des Objekts nicht mehr ändern. Wenn Sie das versuchen, wird ein neues Objekt erstellt.

data class Contact(val name: String, val number: String)

Die zusammensetzbare Funktion ContactRow hat einen Parameter vom Typ Contact.

@Composable
fun ContactRow(contact: Contact, modifier: Modifier = Modifier) {
   var selected by remember { mutableStateOf(false) }

   Row(modifier) {
      ContactDetails(contact)
      ToggleButton(selected, onToggled = { selected = !selected })
   }
}

Überlegen Sie, was passiert, wenn der Nutzer auf die Ein/Aus-Schaltfläche klickt und sich der Status von selected ändert:

  1. Compose prüft, ob der Code in ContactRow neu zusammengesetzt werden soll.
  2. Es wird erkannt, dass das einzige Argument für ContactDetails vom Typ Contact ist.
  3. Da Contact eine unveränderliche Datenklasse ist, kann Compose sicher sein, dass sich keines der Argumente für ContactDetails geändert hat.
  4. Daher wird ContactDetails von Compose übersprungen und nicht neu zusammengesetzt.
  5. Andererseits haben sich die Argumente für ToggleButton geändert und Compose führt ein Recomposition dieser Komponente durch.

Veränderbare Objekte

Im vorherigen Beispiel wird ein unveränderliches Objekt verwendet. Es ist jedoch auch möglich, ein veränderliches Objekt zu erstellen. Sehen Sie sich das folgende Snippet an:

data class Contact(var name: String, var number: String)

Da jeder Parameter von Contact jetzt ein var ist, ist die Klasse nicht mehr unveränderlich. Wenn sich die Eigenschaften ändern, würde Compose das nicht bemerken. Das liegt daran, dass Compose nur Änderungen an Zustandsobjekten von Compose erfasst.

Compose betrachtet eine solche Klasse als instabil. Im strikten Überspringmodus (Kotlin 2.0.20+) vergleicht Compose Contact anhand der Instanzgleichheit (===): Wenn contact.name direkt geändert wird, wird keine Neukomposition ausgelöst. Wenn jedoch eine neu zugewiesene Contact-Instanz mit identischen Werten übergeben wird, wird ContactDetails trotzdem neu zusammengesetzt. Ohne den strikten Überspringmodus wird ContactDetails jedes Mal neu zusammengesetzt, wenn sich selected ändert.

Implementierung in Compose

Es kann hilfreich, aber nicht entscheidend sein, zu wissen, wie genau Compose bestimmt, welche Funktionen während der Neuzusammenstellung übersprungen werden.

Wenn der Compose-Compiler für Ihren Code ausgeführt wird, wird jede Funktion und jeder Typ mit einem von mehreren Tags gekennzeichnet. Diese Tags geben an, wie Compose die Funktion oder den Typ während der Neuzusammenstellung verarbeitet.

Funktionen

Compose kann Funktionen als skippable oder restartable markieren. Eine Funktion kann als eine, beide oder keine der folgenden Optionen markiert werden:

  • Überspringbar: Wenn der Compiler eine Composable-Funktion als überspringbar kennzeichnet, kann Compose sie während der Neukomposition überspringen, wenn alle ihre Argumente mit ihren vorherigen Werten übereinstimmen. Mit dem starken Überspringmodus in Kotlin 2.0.20+ sind alle neu startbaren Composables automatisch überspringbar.
  • Wiederholbar: Eine wiederholbare komponierbare Funktion dient als „Bereich“, in dem die Neukomposition beginnen kann. Mit anderen Worten: Die Funktion kann ein Einstiegspunkt dafür sein, wo Compose nach Zustandsänderungen mit der Neuausführung von Code für die Neuzusammensetzung beginnen kann.

Typen

Markentypen können entweder unveränderlich oder stabil sein. Jeder Typ ist entweder das eine oder das andere:

  • Unveränderlich: In Compose wird ein Typ als unveränderlich gekennzeichnet, wenn sich der Wert seiner Eigenschaften nie ändern kann und alle Methoden referenziell transparent sind.
    • Alle primitiven Typen sind als unveränderlich gekennzeichnet. Dazu gehören String, Int und Float.
  • Stabil: Gibt einen Typ an, dessen Eigenschaften sich nach der Erstellung ändern können. Wenn sich diese Eigenschaften während der Laufzeit ändern, werden diese Änderungen in Compose berücksichtigt.

Stabilität beim Debugging

Wenn Ihre App ein Composable neu zusammensetzt, dessen Parameter sich nicht geändert haben, prüfen Sie zuerst, ob bei jeder Neuzusammensetzung neue Instanzen instabiler Typen zugewiesen werden. Wenn der Modus „Strong skipping“ deaktiviert ist, prüfen Sie, ob das Composable Parameter mit var- oder val-Eigenschaften eines instabilen Typs hat.

Ausführliche Informationen zur Diagnose komplexer Stabilitätsprobleme in Compose finden Sie im Leitfaden Stabilität debuggen.

Stabilitätsprobleme beheben

Informationen dazu, wie Sie die Stabilität Ihrer Compose-Implementierung verbessern können, finden Sie im Leitfaden Stabilitätsprobleme beheben.

Zusammenfassung

Insgesamt sollten Sie Folgendes beachten:

  • Parameter: Compose bestimmt die Stabilität der einzelnen Parameter Ihrer Composables, um zu entscheiden, ob sie während der Neukomposition mit struktureller Gleichheit (.equals()) oder Instanzgleichheit (===) verglichen werden.
  • Sofortige Korrekturen: Wenn Sie feststellen, dass Ihr Composable nicht übersprungen wird und ein Leistungsproblem verursacht, prüfen Sie, ob bei jedem Durchlauf neue Instanzen instabiler Parameter neu erstellt werden oder ob var-Properties anstelle von State verwendet werden.
  • Compilerberichte: Mit Compilerberichten können Sie ermitteln, welche Stabilität für Ihre Klassen abgeleitet wird.
  • Sammlungen: In Compose gelten Standardschnittstellen für Sammlungen (List, Set und Map) als instabil, da ihre zugrunde liegenden Implementierungen möglicherweise veränderlich sind. Im Strong-Skipping-Modus (standardmäßig in Kotlin 2.0.20+ aktiviert) wird die Neuzusammensetzung instabiler Sammlungen weiterhin übersprungen, wenn sich die Sammlungsreferenz nicht ändert. Dazu wird die schnelle O(1)-Instanzgleichheit (===) verwendet. Vermeiden Sie es, große Sammlungen in Kotlinx-immutable-Sammlungen zu konvertieren oder Klassen, die Sammlungen enthalten, mit @Immutable oder @Stable zu annotieren, es sei denn, die strukturelle Gleichheit (O(N) .equals()) ist ausdrücklich erforderlich. Der Vergleich jedes Elements bei jeder Neuzusammensetzung kann teurer sein als die Neuzusammensetzung selbst.
  • Andere Module: In Compose werden Klassen aus Modulen, in denen der Compose-Compiler nicht ausgeführt wird, als instabil betrachtet (im Vergleich zur Verwendung von === im Modus „Starkes Überspringen“). Wenn eine Datenquelle häufig kleine, einfache Modelle aus Nicht-Compose-Modulen mit identischen Werten neu instanziiert, können Sie eine Stabilitätskonfigurationsdatei konfigurieren oder @Stable oder @Immutable verwenden, wenn .equals() kostengünstig ist.

Weitere Informationen