Stabilitätsprobleme beheben

Wenn Sie auf eine instabile Klasse stoßen, die Leistungsprobleme verursacht, sollten Sie sie stabilisieren. In diesem Dokument werden verschiedene Techniken beschrieben, die Sie dafür verwenden können.

Starkes Überspringen aktivieren

Versuchen Sie zuerst, den Modus für schnelles Überspringen zu aktivieren. Im Modus „Starkes Überspringen“ können Composables mit instabilen Parametern übersprungen werden. Dies ist die einfachste Methode, um Leistungsprobleme zu beheben, die durch Stabilitä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 und unveränderliche Typen sein.
    • Primitive Typen wie String, Int und Float sind immer unveränderlich.
    • Wenn das 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 Verhalten von Methoden des Typs bei einem erneuten Aufruf andere Ergebnisse liefern würden.

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 beschrieben, kann der Compose-Compiler nicht mit Sicherheit feststellen, ob Sammlungen wie List, Map und Set wirklich unveränderlich sind. Daher werden sie als instabil gekennzeichnet.

Um dieses Problem zu beheben, können Sie unveränderliche Sammlungen verwenden. Der Compose-Compiler unterstützt Kotlinx Immutable Collections. Diese Sammlungen sind garantiert unveränderlich und der Compose-Compiler behandelt sie entsprechend. Diese Bibliothek befindet sich noch in der Alpha-Phase. Es ist also mit Änderungen an der API zu rechnen.

Sehen Sie sich noch einmal die instabile Klasse aus dem Leitfaden Stabilitätsprobleme diagnostizieren 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 kennzeichnet die Klasse als stabil.

Mit Stable oder Immutable kommentieren

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 über Ihre Klasse ableiten würde. Er ähnelt dem !!-Operator in Kotlin. Sie sollten sehr vorsichtig sein, wie Sie diese Anmerkungen verwenden. Wenn Sie das Compilerverhalten überschreiben, kann dies zu unvorhergesehenen Fehlern führen, z. B. wenn Ihre Composable-Funktion nicht neu zusammengesetzt wird, obwohl Sie das erwarten.

Wenn es möglich ist, Ihre Klasse ohne Annotation stabil zu machen, sollten Sie versuchen, die Stabilität auf diese Weise zu erreichen.

Das folgende Snippet enthält ein einfaches Beispiel für eine Datenklasse, die als unveränderlich annotiert ist:

@Immutable
data class Snack(

)

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

Mit Anmerkungen versehene Klassen in Sammlungen

Stellen Sie sich eine zusammensetzbare Funktion vor, die 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, kennzeichnet der Compose-Compiler den Parameter snacks in HighlightedSnacks weiterhin als instabil.

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

Sie können keinen einzelnen Parameter als stabil kennzeichnen und auch keine Annotation für eine Composable vornehmen, damit sie immer übersprungen werden kann. Es gibt mehrere Möglichkeiten.

Es gibt mehrere 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 Ihrem Code einhalten möchten, können Sie Kotlin-Sammlungen als stabil betrachten, indem Sie kotlin.collections.* zu Ihrer Stabilitätskonfigurationsdatei hinzufügen.

Unveränderliche Sammlung

Für die Kompilierzeitsicherheit der Unveränderlichkeit 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 List in eine annotierte stabile Klasse einfügen. Ein generischer Wrapper ist dafür wahrscheinlich die beste Wahl, je nach Ihren Anforderungen.

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

Sie können diesen dann als Typ des Parameters in Ihrem Composable 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 die HighlightedSnacks-Composable-Funktion als skippable und 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 Neuzusammenstellung kann Compose jetzt HighlightedSnacks überspringen, wenn sich keine der Eingaben geändert hat.

Konfigurationsdatei für die Stabilität

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

Die Konfigurationsdatei ist eine Nur-Text-Datei mit einer Klasse pro Zeile. Kommentare, 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<*,_>

Wenn Sie dieses Feature aktivieren möchten, übergeben Sie den Pfad der Konfigurationsdatei an den Optionsblock composeCompiler der Konfiguration des Gradle-Plug-ins für den Compose-Compiler.

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

Da der Compose-Compiler für jedes Modul in Ihrem Projekt separat ausgeführt wird, können Sie bei Bedarf verschiedene Konfigurationen für verschiedene Module angeben. Alternativ können Sie eine Konfiguration auf der 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 gekennzeichnet 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 dieses Problem auftreten.

Lösung

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

  1. Fügen Sie die Klassen der Compiler-Konfigurationsdatei hinzu.
  2. Aktivieren Sie den Compose-Compiler für Ihre Data Layer-Module oder taggen Sie Ihre Klassen bei Bedarf mit @Stable oder @Immutable.
    • Dazu müssen Sie Ihrer Datenschicht eine Compose-Abhängigkeit hinzufügen. Es ist jedoch nur die Abhängigkeit für die Compose-Laufzeit und nicht für Compose-UI.
  3. Umschließen Sie in Ihrem UI-Modul die Klassen der Datenschicht mit UI-spezifischen Wrapper-Klassen.

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

Nicht jede Composable sollte überspringbar sein

Wenn Sie Stabilitätsprobleme beheben, sollten Sie nicht versuchen, jede Composable überspringbar zu machen. Wenn Sie das versuchen, kann es zu einer vorzeitigen Optimierung kommen, die mehr Probleme verursacht als sie behebt.

Es gibt viele Situationen, in denen das Überspringen keinen wirklichen Vorteil bietet und zu schwer zu wartendem Code führen kann. Beispiel:

  • Ein Composable, das selten oder gar nicht neu zusammengesetzt wird.
  • Eine Composable, die selbst nur überspringbare Composables aufruft.
  • Eine zusammensetzbare Funktion mit einer großen Anzahl von Parametern mit aufwendigen „equals“-Implementierungen. In diesem Fall könnten die Kosten für die Prüfung, ob sich ein Parameter geändert hat, die Kosten für eine kostengünstige Neukomposition übersteigen.

Wenn eine zusammensetzbare Funktion überspringbar ist, entsteht ein geringer Mehraufwand, der sich möglicherweise nicht lohnt. Sie können Ihr Composable sogar als nicht neu startbar kennzeichnen, wenn Sie feststellen, dass ein Neustart mehr Aufwand als Nutzen bedeutet.