Jeśli masz niestabilną klasę, która powoduje problemy z wydajnością, powinna być stabilna. W tym dokumencie opisujemy kilka technik, które można wykorzystać.
Włącz silne pomijanie
Zacznij od włączenia trybu silnego pomijania. Silny tryb pomijania umożliwia pominięcie elementów kompozycyjnych z niestabilnymi parametrami. Jest to najprostsza metoda rozwiązywania problemów z wydajnością spowodowanych stabilnością.
Więcej informacji znajdziesz w sekcji Silne pomijanie.
Ustawianie klasy jako stałej
Możesz też spróbować, aby niestabilna klasa była całkowicie stała.
- Stała: wskazuje typ, w którym wartość właściwości nigdy nie może się zmienić po utworzeniu wystąpienia danego typu, a wszystkie metody są zazwyczaj przejrzyste.
- Upewnij się, że wszystkie właściwości klasy to
val
, a nievar
, i typów stałych. - Typy podstawowe, takie jak
String, Int
czyFloat
, są zawsze stałe. - Jeśli jest to niemożliwe, w przypadku wszystkich zmiennych właściwości musisz używać stanu tworzenia.
- Upewnij się, że wszystkie właściwości klasy to
- Stabilny: oznacza typ zmienny. Środowisko wykonawcze tworzenia nie informuje o tym, czy i kiedy dowolna z publicznych właściwości lub metod działania danego typu dałaby inne wyniki niż w poprzednim wywołaniu.
Kolekcje stałe
Częstą przyczyną, dla której funkcja Compose uważa, że klasa jest niestabilna, są kolekcje. Jak wspomnieliśmy na stronie Diagnozuj problemy ze stabilnością, kompilator Compose nie może mieć całkowitej pewności, że kolekcje takie jak List, Map
i Set
są naprawdę stałe, dlatego są oznaczane jako niestabilne.
Aby rozwiązać ten problem, możesz użyć kolekcji stałych. Kompilator Compose obsługuje kolekcje stałe Kotlinx. Te kolekcje na pewno nie będą się zmieniać, a kompilator Compose traktuje je tak. Ta biblioteka jest nadal w wersji alfa, więc możesz się spodziewać zmian w jej interfejsie API.
Przyjrzyj się jeszcze tej niestabilnej klasie z przewodnika Diagnozuj problemy ze stabilnością:
unstable class Snack {
…
unstable val tags: Set<String>
…
}
Możesz udostępnić usługę tags
stabilną za pomocą kolekcji stałej. W klasie zmień typ tags
na ImmutableSet<String>
:
data class Snack{
…
val tags: ImmutableSet<String> = persistentSetOf()
…
}
Po wykonaniu tej czynności wszystkie parametry klasy są stałe, a kompilator Compose oznacza klasę jako stabilną.
Dodaj adnotację Stable
lub Immutable
Możliwym sposobem rozwiązania problemów ze stabilnością jest dodanie adnotacji do niestabilnych klas za pomocą właściwości @Stable
lub @Immutable
.
Dodanie adnotacji do klasy zastępuje informacje, które kompilator wywnioskował na temat klasy. Jest on podobny do operatora !!
w Kotlin. Ostrożnie
korzystaj z tych adnotacji. Zastąpienie działania kompilatora może prowadzić do nieprzewidzianych błędów, na przykład funkcji kompozycyjnych, które nie powinny zostać utworzone w oczekiwany sposób.
Jeśli możesz, by klasa była stabilna bez adnotacji, postaraj się w ten sposób uzyskać stabilność.
Poniższy fragment kodu zawiera minimalny przykład klasy danych oznaczonej jako niezmienna:
@Immutable
data class Snack(
…
)
Niezależnie od tego, czy używasz adnotacji @Immutable
czy @Stable
, kompilator Compose oznacza klasę Snack
jako stabilną.
Klasy z adnotacjami w kolekcjach
Zastanów się nad funkcją kompozycyjną, która zawiera parametr typu List<Snack>
:
restartable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
…
unstable snacks: List<Snack>
…
)
Nawet jeśli dodasz adnotacje do Snack
za pomocą @Immutable
, kompilator Compose nadal oznacza parametr snacks
w HighlightedSnacks
jako niestabilny.
W przypadku parametrów występuje ten sam problem co klasy w przypadku typów kolekcji. Kompilator Compose zawsze oznacza parametr typu List
jako niestabilny, nawet jeśli jest to zbiór typów stabilnych.
Nie możesz oznaczyć pojedynczych parametrów jako stałych ani dodawać adnotacji do elementu kompozycyjnego, by zawsze był możliwa do pominięcia. Istnieje kilka ścieżek do przodu.
Problem z niestabilnymi kolekcjami możesz rozwiązać na kilka sposobów. W poniższych sekcjach opisujemy poszczególne podejścia.
Plik konfiguracji
Jeśli chcesz zachować zgodność z umową dotyczącą stabilności w swojej bazie kodu, możesz uznać kolekcje Kotlin jako stabilne, dodając kotlin.collections.*
do pliku konfiguracji stabilności.
Stała kolekcja
Aby kompilować bezpieczeństwo czasowe i niezmienność, możesz użyć stałej kolekcji kotlinx zamiast List
.
@Composable
private fun HighlightedSnacks(
…
snacks: ImmutableList<Snack>,
…
)
Wrapper
Jeśli nie możesz użyć kolekcji stałej, możesz utworzyć własną. Aby to zrobić, umieść List
w stabilnej klasie z adnotacjami. W zależności od Twoich wymagań najlepszym wyborem będzie prawdopodobnie
ogólny kod.
@Immutable
data class SnackCollection(
val snacks: List<Snack>
)
Możesz go potem użyć jako typu parametru w funkcji kompozycyjnej.
@Composable
private fun HighlightedSnacks(
index: Int,
snacks: SnackCollection,
onSnackClick: (Long) -> Unit,
modifier: Modifier = Modifier
)
Rozwiązanie
Po zastosowaniu jednego z tych rozwiązań kompilator funkcji Compose oznacza teraz kompozycję HighlightedSnacks
jako zarówno skippable
, jak i 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
)
Podczas zmiany kompozycji funkcja tworzenia może teraz pominąć element HighlightedSnacks
, jeśli żadne dane wejściowe nie zostaną zmienione.
Plik konfiguracji stabilności
Począwszy od wersji 1.5.5 kompilatora tworzenia wiadomości podczas kompilacji można udostępnić plik konfiguracji klas uznawanych za stabilne. Dzięki temu zajęcia, nad którymi nie masz kontroli, takie jak standardowe klasy biblioteki, takie jak LocalDateTime
, mogą zostać uznane za stabilne.
Plik konfiguracji to zwykły plik tekstowy z 1 klasą w każdym wierszu. Obsługiwane są komentarze oraz symbole wieloznaczne pojedyncze i podwójne. Oto przykładowa konfiguracja:
// 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<*,_>
Aby włączyć tę funkcję, przekaż ścieżkę pliku konfiguracji do opcji kompilatora w usłudze Compose.
Odlotowy
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"
)
}
Kompilator Compose działa w każdym module w projekcie oddzielnie, dlatego w razie potrzeby możesz udostępnić różne konfiguracje różnym modułom. Możesz też dodać jedną konfigurację na poziomie głównym projektu i przekazać tę ścieżkę do każdego modułu.
Wiele modułów
Kolejny typowy problem to architektura składająca się z wielu modułów. Kompilator Compose może określić, czy klasa jest stabilna tylko wtedy, gdy wszystkie typy inne niż podstawowe, do których się odwołuje, są wyraźnie oznaczone jako stabilne lub znajdują się w module, który został również utworzony za pomocą kompilatora Compose.
Jeśli warstwa danych znajduje się w osobnym module względem warstwy interfejsu, a jest to zalecane, może występować ten problem.
Rozwiązanie
Aby rozwiązać ten problem, wykonaj jedną z tych czynności:
- Dodaj klasy do pliku konfiguracji kompilacji.
- Włącz kompilator Compose w modułach warstwy danych lub w odpowiednich przypadkach dodaj do klas tag
@Stable
bądź@Immutable
.- Wymaga to dodania do warstwy danych zależności tworzenia. Jest to jednak zależność wyłącznie od środowiska wykonawczego tworzenia wiadomości, a nie od
Compose-UI
.
- Wymaga to dodania do warstwy danych zależności tworzenia. Jest to jednak zależność wyłącznie od środowiska wykonawczego tworzenia wiadomości, a nie od
- W module interfejsu użytkownika dodaj klasy warstwy danych do klas opakowań specyficznych dla UI.
Ten sam problem występuje też w przypadku korzystania z bibliotek zewnętrznych, które nie używają kompilatora Compose.
Nie wszystkie funkcje kompozycyjne muszą być możliwe do pominięcia
Przy rozwiązywaniu problemów ze stabilnością nie próbuj wprowadzać wszystkich funkcji kompozycyjnych do pominięcia. Próba uzyskania dostępu do tych danych może prowadzić do przedwczesnej optymalizacji, która spowoduje więcej problemów niż naprawienie.
Jest wiele sytuacji, w których możliwość pominięcia nie daje żadnych korzyści i może utrudniać utrzymanie kodu. Na przykład:
- Funkcja kompozycyjna, która nie jest ponownie tworzona często lub w ogóle.
- Funkcja kompozycyjna, która sama wywołuje możliwe do pominięcia elementy kompozycyjne.
- Funkcja kompozycyjna o dużej liczbie parametrów i drogimi implementacjami równa się. W tym przypadku koszt sprawdzenia, czy jakiś parametr uległ zmianie, może przeważyć koszt taniej rekompozycji.
Możliwość pominięcia funkcji kompozycyjnej wiąże się z pewnym narzutem, który może być nieopłacalny. Możesz nawet dodać adnotacje do elementu kompozycyjnego, aby nie można go było ponownie uruchomić, jeśli stwierdzisz, że ponowne uruchomienie wymaga większych nakładów niż jest tego warte.