Gdy masz do czynienia z niestabilną klasą, która powoduje problemy z wydajnością , musisz ją ustabilizować. W tym dokumencie opisujemy kilka technik, które możesz zastosować.
Włączanie silnego pomijania
Najpierw spróbuj włączyć tryb silnego pomijania. Tryb silnego pomijania umożliwia pomijanie funkcji kompozycyjnych z niestabilnymi parametrami i jest najłatwiejszą metodą rozwiązywania problemów z wydajnością spowodowanych niestabilnością.
Więcej informacji znajdziesz w artykule Silne pomijanie.
Ustawianie klasy jako niezmiennej
Możesz też spróbować ustawić niestabilną klasę jako całkowicie niezmienną.
- Niezmienna: wskazuje typ, w którym wartości żadnych właściwości nie można
zmienić po utworzeniu instancji tego typu, a wszystkie metody są
referencyjnie przezroczyste.
- Upewnij się, że wszystkie właściwości klasy są typu
valzamiastvari są niezmienne. - Typy pierwotne, takie jak
String, IntiFloat, są zawsze niezmienne. - Jeśli jest to niemożliwe, musisz użyć stanu Compose w przypadku wszystkich właściwości zmiennych.
- Upewnij się, że wszystkie właściwości klasy są typu
- Stabilna: wskazuje typ, który jest zmienny. Środowisko wykonawcze Compose nie wie, czy i kiedy właściwości publiczne lub zachowanie metody typu dałyby inne wyniki niż poprzednie wywołanie.
Kolekcje niezmienne
Częstym powodem, dla którego Compose uważa klasę za niestabilną, są kolekcje. Jak wspomnieliśmy
na stronie Diagnozowanie problemów ze stabilnością, kompilator Compose
nie może mieć całkowitej pewności, że kolekcje takie jak List, Map, i Set są
rzeczywiście niezmienne, dlatego oznacza je jako niestabilne.
Aby rozwiązać ten problem, możesz użyć kolekcji niezmiennych. Kompilator Compose obsługuje kolekcje niezmienne Kotlinx. Te kolekcje są zaprojektowane jako niezmienne, a kompilator Compose traktuje je jako takie. Ta biblioteka jest nadal w wersji alfa, więc mogą w niej wystąpić zmiany w interfejsie API.
Ponownie rozważ tę niestility klasę z przewodnika Diagnozowanie problemów ze stabilnością:
unstable class Snack {
…
unstable val tags: Set<String>
…
}
Możesz ustabilizować tags za pomocą kolekcji niezmiennej. W zajęciach zmień
typ tags na ImmutableSet<String>:
data class Snack{
…
val tags: ImmutableSet<String> = persistentSetOf()
…
}
Po wykonaniu tej czynności wszystkie parametry klasy są niezmienne, a kompilator Compose oznacza klasę jako stabilną.
Dodawanie adnotacji Stable lub Immutable
Jednym ze sposobów rozwiązania problemów ze stabilnością jest dodanie do niestabilnych klas adnotacji @Stable lub @Immutable.
Dodanie adnotacji do klasy powoduje zastąpienie tego, co kompilator wywnioskowałby inaczej
wywnioskował o Twojej klasie. Jest to podobne do
!! operatora w Kotlinie. Musisz bardzo uważać na to, jak używasz tych adnotacji. Zastąpienie zachowania kompilatora może spowodować nieprzewidziane błędy, np. brak ponownego komponowania funkcji kompozycyjnej, gdy tego oczekujesz.
Jeśli możesz ustabilizować klasę bez adnotacji, powinieneś dążyć do osiągnięcia stabilności w ten sposób.
Poniższy fragment kodu zawiera minimalny przykład klasy danych z adnotacją wskazującą, że jest ona 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
Rozważmy 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 do Snack adnotację @Immutable, kompilator Compose nadal będzie oznaczać
parametr snacks w HighlightedSnacks jako niestabilny.
W przypadku typów kolekcji parametry mają ten sam problem co klasy –
kompilator Compose zawsze oznacza parametr typu List jako niestabilny, nawet
jeśli jest to kolekcja typów stabilnych.
Nie możesz oznaczyć pojedynczego parametru jako stabilnego ani dodać adnotacji do funkcji kompozycyjnej, aby zawsze można było ją pominąć. Istnieje kilka sposobów rozwiązania tego problemu.
Istnieje kilka sposobów obejścia problemu z niestabilnymi kolekcjami. W kolejnych podsekcjach opisujemy te różne podejścia.
Plik konfiguracji
Jeśli chcesz przestrzegać umowy dotyczącej stabilności w swojej bazie kodu, możesz włączyć traktowanie kolekcji Kotlin jako stabilnych, dodając kotlin.collections.* do swojego pliku konfiguracji stabilności.
Kolekcja niezmienna
Aby zapewnić bezpieczeństwo niezmienności w czasie kompilacji, możesz użyć kolekcji niezmiennej kotlinx zamiast List.
@Composable
private fun HighlightedSnacks(
…
snacks: ImmutableList<Snack>,
…
)
Wrapper
Jeśli nie możesz użyć kolekcji niezmiennej, możesz utworzyć własną. Aby to zrobić, umieść List w klasie stabilnej z adnotacją. W zależności od wymagań najlepszym rozwiązaniem może być ogólny wrapper.
@Immutable
data class SnackCollection(
val snacks: List<Snack>
)
Następnie możesz użyć go 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 podejść kompilator Compose oznacza teraz funkcję kompozycyjną HighlightedSnacks jako skippable 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 rekompozycji Compose może teraz pominąć HighlightedSnacks, jeśli żadne z jej danych wejściowych nie uległy zmianie.
Plik konfiguracji stabilności
Od wersji 1.5.5 kompilatora Compose podczas kompilacji można podać plik konfiguracji klas, które mają być traktowane jako stabilne. Umożliwia to traktowanie jako stabilnych klas, nad którymi nie masz kontroli, np. klas biblioteki standardowej, takich jak LocalDateTime.
Plik konfiguracji to zwykły plik tekstowy, w którym w każdym wierszu znajduje się jedna klasa. Obsługiwane są komentarze oraz pojedyncze i podwójne symbole wieloznaczne.
Przykład konfiguracji:
// 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<*,_>
Aby włączyć tę funkcję, przekaż ścieżkę do pliku konfiguracji do bloku opcji
composeCompiler wtyczki Gradle kompilatora Compose
konfiguracji.
composeCompiler {
stabilityConfigurationFile = rootProject.layout.projectDirectory.file("stability_config.conf")
}
Kompilator Compose działa oddzielnie w każdym module projektu, więc w razie potrzeby możesz podać różne konfiguracje dla różnych modułów. Możesz też mieć jedną konfigurację na poziomie głównym projektu i przekazać tę ścieżkę do każdego modułu.
Wiele modułów
Inny częsty problem dotyczy architektury wielomodułowej. Kompilator Compose może wywnioskować, czy klasa jest stabilna, tylko wtedy, gdy wszystkie typy inne niż pierwotne, do których się odwołuje, są wyraźnie oznaczone jako stabilne lub znajdują się w module, który został też zbudowany za pomocą kompilatora Compose.
Jeśli warstwa danych znajduje się w osobnym module niż warstwa interfejsu, co jest zalecanym rozwiązaniem, może to być problem, który napotkasz.
Rozwiązanie
Aby rozwiązać ten problem, możesz zastosować jedno z tych podejść:
- Dodaj klasy do pliku konfiguracji kompilatora.
- Włącz kompilator Compose w modułach warstwy danych lub oznacz klasy adnotacjami
@Stablelub@Immutable.- Wymaga to dodania zależności Compose do warstwy danych. Jest to jednak tylko zależność od środowiska wykonawczego Compose, a nie od
Compose-UI.
- Wymaga to dodania zależności Compose do warstwy danych. Jest to jednak tylko zależność od środowiska wykonawczego Compose, a nie od
- W module interfejsu umieść klasy warstwy danych w klasach wrapperów specyficznych dla interfejsu.
Ten sam problem występuje też w przypadku korzystania z bibliotek zewnętrznych, jeśli nie używają one kompilatora Compose.
Nie każda funkcja kompozycyjna powinna być pomijalna
Podczas rozwiązywania problemów ze stabilnością nie należy próbować sprawić, aby każda funkcja kompozycyjna była pomijalna. Próba zrobienia tego może prowadzić do przedwczesnej optymalizacji, która wprowadza więcej problemów niż rozwiązuje.
W wielu sytuacjach pomijalność nie przynosi żadnych korzyści i może prowadzić do kodu, który jest trudny w utrzymaniu. Przykład:
- Funkcja kompozycyjna, która nie jest ponownie komponowana często lub wcale.
- Funkcja kompozycyjna, która sama w sobie wywołuje tylko pomijalne funkcje kompozycyjne.
- Funkcja kompozycyjna z dużą liczbą parametrów z kosztownymi implementacjami equals. W takim przypadku koszt sprawdzenia, czy którykolwiek parametr uległ zmianie, może przewyższać koszt taniego ponownego komponowania.
Gdy funkcja kompozycyjna jest pomijalna, dodaje to niewielki narzut, który może być nieopłacalny. Możesz nawet dodać adnotację do funkcji kompozycyjnej, aby była nieuruchamialna ponownie w przypadkach , gdy stwierdzisz, że możliwość ponownego uruchomienia jest większym narzutem niż korzyścią.