Wenn eine instabile Klasse zu Leistungsproblemen führt, sollten Sie sie stabilisieren. In diesem Dokument werden verschiedene Techniken beschrieben, die Sie dazu verwenden können.
Starkes Überspringen aktivieren
Du solltest zuerst versuchen, den Modus für starkes Überspringen zu aktivieren. Der starke Überspringungsmodus ermöglicht es, zusammensetzbare Funktionen mit instabilen Parametern zu überspringen. Dies ist die einfachste Methode, um Leistungsprobleme aufgrund der Stabilität zu beheben.
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 Attributen nie mehr ändern kann, nachdem eine Instanz dieses Typs erstellt wurde. Außerdem sind alle Methoden referenziell transparent.
- Alle Attribute der Klasse müssen sowohl
val
als auchvar
sein und unveränderliche Typen haben. - Primitive Typen wie
String, Int
undFloat
sind immer unveränderlich. - Wenn dies nicht möglich ist, müssen Sie für alle änderbaren Attribute den Status „Compose“ verwenden.
- Alle Attribute der Klasse müssen sowohl
- Stabil: Gibt einen Typ an, der änderbar ist. Die Compose-Laufzeit erkennt nicht, ob und wann die öffentlichen Attribute oder das Methodenverhalten des Typs zu anderen Ergebnissen für einen vorherigen Aufruf führen würden.
Unveränderliche Sammlungen
Ein häufiger Grund dafür, dass die Klasse „Compose“ als instabil einstuft, sind Sammlungen. Wie auf der Seite Stabilitätsprobleme diagnostizieren erwähnt, kann der Compose-Compiler nicht ganz sicher sein, ob Sammlungen wie List, Map
und Set
wirklich unveränderlich sind und daher als instabil gekennzeichnet werden.
Um dieses Problem zu lösen, können Sie unveränderliche Sammlungen verwenden. Der Compose-Compiler unterstützt Kotlinx-unveränderliche Sammlungen. Diese Sammlungen sind garantiert unveränderlich und der Compose-Compiler behandelt sie als solche. Diese Bibliothek befindet sich noch in der Alphaphase. Sie sollten daher mit möglichen Änderungen an der API rechnen.
Sehen Sie sich noch einmal diese instabile Klasse aus der Anleitung Stabilitätsprobleme diagnostizieren an:
unstable class Snack {
…
unstable val tags: Set<String>
…
}
Sie können tags
mithilfe 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 zur Behebung von Stabilitätsproblemen besteht darin, instabile Klassen entweder mit @Stable
oder @Immutable
zu annotieren.
Durch das Annotieren einer Klasse wird überschrieben, was der Compiler sonst über die Klasse ableiten würde. Er ähnelt dem Operator !!
in Kotlin. Sie sollten bei der Verwendung
dieser Anmerkungen sehr vorsichtig sein. Wenn Sie das Compiler-Verhalten überschreiben, kann es zu unerwarteten Fehlern kommen, z. B. dass Ihre zusammensetzbare Funktion nicht wie erwartet neu zusammengesetzt wird.
Wenn es möglich ist, Ihre Klasse ohne Annotation stabil zu machen, sollten Sie auf diese Weise Stabilität erreichen.
Das folgende Snippet enthält ein minimales 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, markiert der Compose-Compiler die Snack
-Klasse als stabil.
Annotierte Klassen in Sammlungen
Hier sehen Sie eine zusammensetzbare Funktion, 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, markiert der Compose-Compiler den snacks
-Parameter in HighlightedSnacks
als instabil.
Parameter haben in Bezug auf Sammlungstypen dasselbe Problem wie Klassen: Der Compiler Compose markiert einen Parameter des Typs List
immer als instabil, auch wenn es sich um eine Sammlung stabiler Typen handelt.
Es ist nicht möglich, einzelne Parameter als stabil zu markieren oder eine zusammensetzbare Funktion so zu annotieren, dass sie immer überspringbar ist. Es gibt mehrere Pfade.
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 Ihrer Codebasis einhalten möchten, können Sie Kotlin-Sammlungen als stabil betrachten, indem Sie kotlin.collections.*
in die Stabilitätskonfigurationsdatei einfügen.
Unveränderliche Sammlung
Um die Sicherheit bei der Kompilierungszeit und die Unveränderlichkeit 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 Sammlung erstellen. Dazu verpacken Sie den List
in eine annotierte stabile Klasse. Ein generischer Wrapper ist abhängig von Ihren Anforderungen wahrscheinlich die beste Wahl dafür.
@Immutable
data class SnackCollection(
val snacks: List<Snack>
)
Diesen können Sie dann als Parametertyp in Ihrer zusammensetzbaren Funktion verwenden.
@Composable
private fun HighlightedSnacks(
index: Int,
snacks: SnackCollection,
onSnackClick: (Long) -> Unit,
modifier: Modifier = Modifier
)
Die Lösung
Nach einem dieser Ansätze markiert der Compose-Compiler die zusammensetzbare Funktion HighlightedSnacks
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 Neuzusammensetzung kann HighlightedSnacks
jetzt übersprungen werden, wenn sich keine Eingabe geändert hat.
Stabilitätskonfigurationsdatei
Ab Compose Compiler 1.5.5 kann während der Kompilierung eine Konfigurationsdatei mit Klassen bereitgestellt werden, die als stabil gelten sollen. Dadurch können Sie Klassen, die Sie nicht steuern können, z. B. Standardbibliotheksklassen wie LocalDateTime
, als stabil betrachten.
Die Konfigurationsdatei ist eine Nur-Text-Datei mit einer Klasse pro Zeile. Kommentare sowie einfache und doppelte Platzhalter werden unterstützt. Hier eine Beispielkonfiguration:
// Consider LocalDateTime stable
java.time.LocalDateTime
// Consider kotlin collections stable
kotlin.collections.*
// 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<*,_>
Übergeben Sie den Pfad der Konfigurationsdatei an die Compiler-Optionen „Compose“, um dieses Feature zu aktivieren.
Groovig
kotlinOptions {
freeCompilerArgs += [
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:stabilityConfigurationPath=" +
project.absolutePath + "/compose_compiler_config.conf"
]
}
Kotlin
kotlinOptions {
freeCompilerArgs += listOf(
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:stabilityConfigurationPath=" +
"${project.absolutePath}/compose_compiler_config.conf"
)
}
Da der Compose-Compiler für jedes Modul in Ihrem Projekt separat ausgeführt wird, können Sie bei Bedarf unterschiedliche Konfigurationen für verschiedene Module bereitstellen. Alternativ können Sie eine Konfiguration auf der Stammebene Ihres Projekts einrichten und diesen Pfad an jedes Modul übergeben.
Mehrere Module
Ein weiteres häufiges Problem betrifft die Multi-Modul-Architektur. Der Compiler Compose kann nur dann ableiten, ob eine Klasse stabil ist, wenn alle nicht-primitiven Typen, auf die er verweist, entweder explizit als stabil gekennzeichnet oder in einem Modul enthalten ist, das ebenfalls mit dem Compose-Compiler erstellt wurde.
Dieses Problem kann auftreten, wenn sich die Datenschicht in einem anderen Modul als die UI-Ebene befindet, was der empfohlene Ansatz ist.
Die Lösung
Sie haben folgende Möglichkeiten, das Problem zu lösen:
- Fügen Sie die Klassen der Compiler-Konfigurationsdatei hinzu.
- Aktivieren Sie den Compose-Compiler für Ihre Datenschichtmodule oder kennzeichnen Sie Ihre Klassen gegebenenfalls mit
@Stable
oder@Immutable
.- Dazu müssen Sie der Datenschicht eine Abhängigkeit vom Typ „Compose“ hinzufügen. Es handelt sich jedoch nur um die Abhängigkeit für die Compose-Laufzeit und nicht für
Compose-UI
.
- Dazu müssen Sie der Datenschicht eine Abhängigkeit vom Typ „Compose“ hinzufügen. Es handelt sich jedoch nur um die Abhängigkeit für die Compose-Laufzeit und nicht für
- Innerhalb Ihres UI-Moduls verpacken Sie Ihre Datenschichtklassen in UI-spezifische Wrapper-Klassen.
Das gleiche Problem tritt auch auf, wenn externe Bibliotheken verwendet werden, die nicht den Composer-Compiler verwenden.
Nicht jede zusammensetzbare Funktion sollte überspringbar sein
Wenn du Stabilitätsprobleme beheben möchtest, solltest du nicht versuchen, jede zusammensetzbare Funktion überspringbar zu machen. Dies kann zu einer vorzeitigen Optimierung führen, die mehr Probleme mit sich bringt, als behoben werden kann.
Es gibt viele Situationen, in denen die Möglichkeit, überspringbar zu sein, keinen echten Vorteil bietet und den Code möglicherweise nur schwer verwalten kann. Beispiele:
- Eine zusammensetzbare Funktion, die nur selten oder überhaupt neu zusammengesetzt wird.
- Eine zusammensetzbare Funktion, die selbst nur „überspringbare zusammensetzbare Funktionen“ bezeichnet.
- Eine zusammensetzbare Funktion mit einer großen Anzahl von Parametern und teuren Equal-Implementierungen. In diesem Fall können die Kosten für die Überprüfung, ob sich ein Parameter geändert hat, die Kosten für eine kostengünstige Neuzusammensetzung überwiegen.
Wenn eine zusammensetzbare Funktion überspringbar ist, entsteht dadurch ein geringer Aufwand, der sich möglicherweise nicht lohnt. Sie können die zusammensetzbare Funktion sogar als nicht neustartbar kennzeichnen, wenn Sie feststellen, dass die Funktion für einen Neustart mehr Aufwand verursacht, als es wert ist.