Stabilitätsprobleme beheben

Wenn Sie mit einer instabilen Klasse konfrontiert sind, die Leistungsprobleme verursacht, sollten Sie sie stabil machen. In diesem Dokument werden verschiedene Techniken beschrieben, mit denen Sie dies tun können.

Starkes Überspringen aktivieren

Sie sollten zuerst versuchen, den Modus für starkes Überspringen zu aktivieren. In diesem Modus können Composables mit instabilen Parametern übersprungen werden. Dies ist die einfachste Methode, um Leistungsprobleme zu beheben, die durch Instabilität verursacht werden.

Weitere Informationen finden Sie unter Starkes Überspringen.

Klasse unveränderlich machen

Sie können auch versuchen, eine instabile Klasse vollständig unveränderlich zu machen.

  • Unveränderlich: Gibt einen Typ an, bei dem sich der Wert von Eigenschaften nach der Erstellung einer Instanz dieses Typs nie ändern kann und alle Methoden referenziell transparent sind.
    • Alle Eigenschaften der Klasse müssen sowohl val als auch var, unveränderliche Typen sein.
    • Primitive Typen wie String, Int und Float sind immer unveränderlich.
    • Wenn dies nicht möglich ist, müssen Sie den Compose-Status für alle veränderlichen Eigenschaften verwenden.
  • Stabil: Gibt einen Typ an, der veränderlich ist. Die Compose-Laufzeit erkennt nicht, ob und wann öffentliche Eigenschaften oder das Methodenverhalten des Typs bei einem Aufruf andere Ergebnisse liefern als bei einem vorherigen Aufruf.

Unveränderliche Sammlungen

Ein häufiger Grund dafür, dass Compose eine Klasse als instabil betrachtet, sind Sammlungen. Wie auf der Seite Stabilitätsprobleme diagnostizieren erwähnt, kann der Compose-Compiler nicht vollständig sicher sein, dass Sammlungen wie List, Map, und Set wirklich unveränderlich sind, und markiert sie daher als instabil.

Um dieses Problem zu beheben, können Sie unveränderliche Sammlungen verwenden. Der Compose-Compiler unterstützt unveränderliche Kotlinx-Sammlungen. Diese Sammlungen sind so konzipiert, dass sie unveränderlich sind, und der Compose-Compiler behandelt sie auch so. Diese Bibliothek befindet sich noch in der Alpha-Phase. Möglicherweise werden Änderungen an der API vorgenommen.

Sehen Sie sich noch einmal diese instabile Klasse aus dem Diagnose stability issues Leitfaden an:

unstable class Snack {
  
  unstable val tags: Set<String>
  
}

Sie können tags mit einer unveränderlichen Sammlung stabil machen. Ändern Sie in der Klasse den Typ von tags in ImmutableSet<String>:

data class Snack{
    
    val tags: ImmutableSet<String> = persistentSetOf()
    
}

Danach sind alle Parameter der Klasse unveränderlich und der Compose-Compiler markiert die Klasse als stabil.

Mit Stable oder Immutable annotieren

Eine mögliche Lösung für Stabilitätsprobleme besteht darin, instabile Klassen mit @Stable oder @Immutable zu annotieren.

Durch das Annotieren einer Klasse wird überschrieben, was der Compiler sonst ableiten würde. Es ähnelt dem !! Operator in Kotlin. Sie sollten sehr vorsichtig sein, wie Sie diese Annotationen verwenden. Wenn Sie das Compilerverhalten überschreiben, kann dies zu unvorhergesehenen Fehlern führen, z. B. dazu, dass Ihr Composables nicht neu zusammengesetzt wird, wenn Sie es erwarten.

Wenn es möglich ist, Ihre Klasse ohne Annotation stabil zu machen, sollten Sie dies tun.

Der folgende Snippet enthält ein einfaches Beispiel für eine als unveränderlich annotierte Datenklasse:

@Immutable
data class Snack(

)

Unabhängig davon, ob Sie die Annotation @Immutable oder @Stable verwenden, markiert der Compose-Compiler die Klasse Snack als stabil.

Annotierte Klassen in Sammlungen

Sehen Sie sich ein Composables an, das einen Parameter vom Typ List<Snack> enthält:

restartable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
  
  unstable snacks: List<Snack>
  
)

Auch wenn Sie Snack mit @Immutable annotieren, markiert den snacks Parameter in HighlightedSnacks weiterhin als instabil.

Bei Parametern tritt dasselbe Problem wie bei Klassen auf, wenn es um Sammlungstypen geht. Der Compose-Compiler markiert einen Parameter vom Typ List immer als instabil, auch wenn es sich um eine Sammlung stabiler Typen handelt.

Sie können einen einzelnen Parameter nicht als stabil markieren und auch kein Composables so annotieren, dass es immer übersprungen werden kann. Es gibt mehrere Möglichkeiten, weiter vorzugehen.

Es gibt verschiedene Möglichkeiten, das Problem instabiler Sammlungen zu umgehen. In den folgenden Unterabschnitten werden diese verschiedenen Ansätze beschrieben.

Konfigurationsdatei

Wenn Sie den Stabilitätsvertrag in Ihrer Codebasis einhalten möchten, dann können Sie Kotlin-Sammlungen als stabil betrachten, indem Sie kotlin.collections.* zu Ihrer Stabilitätskonfigurationsdatei hinzufügen.

Unveränderliche Sammlung

Um die Unveränderlichkeit zur Kompilierzeit zu gewährleisten, können Sie anstelle von List eine unveränderliche Kotlinx-Sammlung verwenden.

@Composable
private fun HighlightedSnacks(
    
    snacks: ImmutableList<Snack>,
    
)

Wrapper

Wenn Sie keine unveränderliche Sammlung verwenden können, können Sie eine eigene erstellen. Dazu müssen Sie die List in eine annotierte stabile Klasse einfügen. Ein generischer Wrapper ist je nach Ihren Anforderungen wahrscheinlich die beste Wahl.

@Immutable
data class SnackCollection(
   val snacks: List<Snack>
)

Sie können dies dann als Typ des Parameters in Ihrem Composables verwenden.

@Composable
private fun HighlightedSnacks(
    index: Int,
    snacks: SnackCollection,
    onSnackClick: (Long) -> Unit,
    modifier: Modifier = Modifier
)

Lösung

Nachdem Sie einen dieser Ansätze gewählt haben, markiert der Compose-Compiler das Composables HighlightedSnacks jetzt sowohl als skippable als auch als restartable.

restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
  stable index: Int
  stable snacks: ImmutableList<Snack>
  stable onSnackClick: Function1<Long, Unit>
  stable modifier: Modifier? = @static Companion
)

Bei der Neukomposition kann Compose HighlightedSnacks jetzt überspringen, wenn sich keine der Eingaben geändert hat.

Stabilitätskonfigurationsdatei

Ab Compose-Compiler 1.5.5 kann zur Kompilierzeit eine Konfigurationsdatei mit Klassen bereitgestellt werden, die als stabil betrachtet werden sollen. So können Sie Klassen, die Sie nicht steuern, z. B. Standardklassen wie LocalDateTime, als stabil betrachten.

Die Konfigurationsdatei ist eine Nur-Text-Datei mit einer Klasse pro Zeile. Kommentare sowie einzelne und doppelte Platzhalter werden unterstützt.

Hier eine Beispielkonfiguration:

// Consider LocalDateTime stable
java.time.LocalDateTime
// Consider my datalayer stable
com.datalayer.*
// Consider my datalayer and all submodules stable
com.datalayer.**
// Consider my generic type stable based off it's first type parameter only
com.example.GenericClass<*,_>

Um diese Funktion zu aktivieren, übergeben Sie den Pfad der Konfigurationsdatei an den composeCompiler Optionsblock der Compose compiler Gradle plugin Konfiguration.

composeCompiler {
  stabilityConfigurationFile = rootProject.layout.projectDirectory.file("stability_config.conf")
}

Da der Compose-Compiler in jedem Modul Ihres Projekts separat ausgeführt wird, können Sie bei Bedarf verschiedene Konfigurationen für verschiedene Module bereitstellen. Alternativ können Sie eine Konfiguration auf Stammebene Ihres Projekts haben und diesen Pfad an jedes Modul übergeben.

Mehrere Module

Ein weiteres häufiges Problem betrifft die Architektur mit mehreren Modulen. Der Compose-Compiler kann nur ableiten, ob eine Klasse stabil ist, wenn alle nicht primitiven Typen, auf die sie verweist, entweder explizit als stabil markiert sind oder sich in einem Modul befinden, das ebenfalls mit dem Compose-Compiler erstellt wurde.

Wenn sich Ihre Datenschicht in einem separaten Modul von Ihrer UI-Schicht befindet, was der empfohlene Ansatz ist, kann dies ein Problem sein.

Lösung

Sie haben folgende Möglichkeiten, dieses Problem zu beheben:

  1. Fügen Sie die Klassen Ihrer Compiler-Konfigurationsdatei hinzu.
  2. Aktivieren Sie den Compose-Compiler in Ihren Modulen der Datenschicht oder taggen Sie Ihre Klassen gegebenenfalls mit @Stable oder @Immutable.
    • Dazu müssen Sie Ihrer Datenschicht eine Compose-Abhängigkeit hinzufügen. Es handelt sich jedoch nur um die Abhängigkeit für die Compose-Laufzeit und nicht für Compose-UI.
  3. Fügen Sie in Ihrem UI-Modul Ihre Klassen der Datenschicht in UI-spezifische Wrapper-Klassen ein.

Dasselbe Problem tritt auch bei der Verwendung externer Bibliotheken auf, wenn diese den Compose-Compiler nicht verwenden.

Nicht jedes Composables sollte übersprungen werden können

Wenn Sie Probleme mit der Stabilität beheben, sollten Sie nicht versuchen, jedes Composables überspringbar zu machen. Wenn Sie dies versuchen, kann es zu einer vorzeitigen Optimierung kommen, die mehr Probleme verursacht als behebt.

Es gibt viele Situationen, in denen das Überspringen keine wirklichen Vorteile bietet und zu schwer wartbarem Code führen kann. Beispiel:

  • Ein Composables, das nicht oft oder gar nicht neu zusammengesetzt wird.
  • Ein Composables, das selbst nur überspringbare Composables aufruft.
  • Ein Composables mit einer großen Anzahl von Parametern mit aufwendigen Implementierungen von „equals“. In diesem Fall könnten die Kosten für die Überprüfung, ob sich ein Parameter geändert hat, die Kosten für eine günstige Neuzusammensetzung übersteigen.

Wenn ein Composables überspringbar ist, entsteht ein geringer Mehraufwand, der sich möglicherweise nicht lohnt. Sie können Ihr Composables sogar so annotieren, dass es nicht neu gestartet werden kann, wenn Sie feststellen , dass der Mehraufwand für den Neustart höher ist als der Nutzen.