Compose uznaje typy za stabilne lub niestabilne. Typ jest stabilny, jeśli jest niezmienny lub jeśli Compose może określić, czy jego wartość zmieniła się między rekompozycjami. Typ jest niestabilny, jeśli Compose nie może określić, czy jego wartość zmieniła się między rekompozycjami.
Compose używa stabilności parametrów funkcji kompozycyjnej, aby określić, jak porównywać dane wejściowe i zdecydować, czy może pominąć funkcję kompozycyjną podczas rekompozycji (z trybem silnego pomijania domyślnie włączonym od wersji Kotlin 2.0.20):
- Parametry stabilne: Compose porównuje parametry stabilne, używając równości strukturalnej (
Object.equals()). Jeśli parametry stabilne funkcji kompozycyjnej są równe ich poprzednim wartościom, Compose pomija tę funkcję. - Parametry niestabilne: gdy włączony jest tryb silnego pomijania (domyślny w Kotlin 2.0.20 i nowszych), Compose porównuje parametry niestabilne za pomocą równości instancji (
===) i pomija funkcję kompozycyjną, jeśli przekazywane są te same instancje obiektów. W wersjach starszych niż Kotlin 2.0.20 (lub gdy tryb silnego pomijania jest wyłączony) Compose zawsze ponownie komponuje funkcję kompozycyjną z niestabilnymi parametrami, gdy jej element nadrzędny jest ponownie komponowany.
Jeśli aplikacja często przydziela nowe instancje niestabilnych parametrów lub wymusza kosztowne kontrole .equals() w dużych kolekcjach przez nadmierne dodawanie adnotacji do modeli, możesz zauważyć niepotrzebne rekompozycje lub narzut związany z porównywaniem.
W tym dokumencie szczegółowo opisujemy, jak Compose określa stabilność i jak możesz ją zoptymalizować, aby poprawić wydajność i ogólne wrażenia użytkowników.
Obiekty niezmienne
Poniższe fragmenty kodu pokazują ogólne zasady stabilności i rekompozycji.
Klasa Contact to niezmienna klasa danych. Dzieje się tak, ponieważ wszystkie parametry są typami podstawowymi zdefiniowanymi za pomocą słowa kluczowego val. Po utworzeniu instancji Contact nie można zmienić wartości właściwości obiektu.
Jeśli spróbujesz to zrobić, utworzysz nowy obiekt.
data class Contact(val name: String, val number: String)
Funkcja kompozycyjna ContactRow ma parametr typu Contact.
@Composable
fun ContactRow(contact: Contact, modifier: Modifier = Modifier) {
var selected by remember { mutableStateOf(false) }
Row(modifier) {
ContactDetails(contact)
ToggleButton(selected, onToggled = { selected = !selected })
}
}
Zastanów się, co się stanie, gdy użytkownik kliknie przycisk przełączania i zmieni się stan selected:
- Compose ocenia, czy należy ponownie skomponować kod wewnątrz
ContactRow. - Widzi, że jedyny argument funkcji
ContactDetailsjest typuContact. Contactjest niezmienną klasą danych, więc Compose ma pewność, że żaden z argumentów funkcjiContactDetailsnie uległ zmianie.- Dlatego Compose pomija element
ContactDetailsi nie przeprowadza jego rekompozycji. - Z drugiej strony argumenty funkcji
ToggleButtonuległy zmianie, więc Compose ponownie komponuje ten komponent.
Obiekty zmienne
W poprzednim przykładzie użyto obiektu niezmiennego, ale można też utworzyć obiekt zmienny. Rozważmy ten fragment kodu:
data class Contact(var name: String, var number: String)
Każdy parametr Contact jest teraz var, więc klasa nie jest już niezmienna.
Jeśli jego właściwości ulegną zmianie, Compose nie będzie o tym wiedzieć. Dzieje się tak, ponieważ Compose śledzi tylko zmiany w obiektach State Compose.
Compose uznaje taką klasę za niestabilną. W trybie silnego pomijania (Kotlin 2.0.20+) Compose porównuje Contact, używając równości instancji (===): mutowanie contact.name w miejscu nie spowoduje rekompozycji, a przekazanie nowo przydzielonej instancji Contact z identycznymi wartościami nadal wymusi rekompozycję ContactDetails (bez trybu silnego pomijania ContactDetails rekomponuje się za każdym razem, gdy zmienia się selected).
Implementacja w Compose
Warto, choć nie jest to konieczne, zastanowić się, jak dokładnie Compose określa, które funkcje pominąć podczas rekompozycji.
Gdy kompilator Compose działa na Twoim kodzie, oznacza każdą funkcję i typ jednym z kilku tagów. Te tagi odzwierciedlają sposób, w jaki Compose obsługuje funkcję lub typ podczas rekompozycji.
Funkcje
Compose może oznaczać funkcje jako skippable lub restartable. Pamiętaj, że może ona oznaczyć funkcję jako jedną z nich, obie lub żadną z nich:
- Możliwość pominięcia: jeśli kompilator oznaczy funkcję kompozycyjną jako możliwą do pominięcia, Compose może ją pominąć podczas rekompozycji, jeśli wszystkie jej argumenty są równe poprzednim wartościom. (W przypadku trybu silnego pomijania w Kotlin 2.0.20 lub nowszym wszystkie elementy kompozycyjne, które można uruchomić ponownie, można automatycznie pominąć).
- Z możliwością ponownego uruchomienia: funkcja kompozycyjna, którą można ponownie uruchomić, służy jako „zakres”, w którym można rozpocząć rekompozycję. Innymi słowy, funkcja może być punktem wejścia, w którym Compose może zacząć ponownie wykonywać kod w celu rekompozycji po zmianie stanu.
Typy
Compose oznacza typy jako niezmienne lub stabilne. Każdy typ jest jednym z tych dwóch:
- Niezmienne: Compose oznacza typ jako niezmienny, jeśli wartości jego właściwości nigdy nie mogą się zmienić, a wszystkie metody są referencyjnie przezroczyste.
- Pamiętaj, że wszystkie typy podstawowe są oznaczone jako niezmienne. Są to:
String,IntiFloat.
- Pamiętaj, że wszystkie typy podstawowe są oznaczone jako niezmienne. Są to:
- Stabilny: wskazuje typ, którego właściwości mogą się zmienić po utworzeniu. Jeśli te właściwości zmienią się w czasie działania, Compose dowie się o tych zmianach.
Stabilność debugowania
Jeśli aplikacja ponownie tworzy element kompozycyjny, którego parametry się nie zmieniły, najpierw sprawdź, czy przy każdej rekompozycji są przydzielane nowe instancje niestabilnych typów (lub, jeśli tryb silnego pomijania jest wyłączony, czy element kompozycyjny ma parametry z właściwościami var lub val niestabilnego typu).
Szczegółowe informacje o diagnozowaniu złożonych problemów ze stabilnością w Compose znajdziesz w przewodniku Debugowanie stabilności.
Rozwiązywanie problemów ze stabilnością
Informacje o tym, jak zapewnić stabilność implementacji Compose, znajdziesz w przewodniku Rozwiązywanie problemów ze stabilnością.
Podsumowanie
Ogólnie rzecz biorąc, pamiętaj o tych kwestiach:
- Parametry: Compose określa stabilność każdego parametru funkcji kompozycyjnych, aby zdecydować, czy podczas rekompozycji porównywać je za pomocą równości strukturalnej (
.equals()) czy równości instancji (===). - Natychmiastowe rozwiązania: jeśli zauważysz, że funkcja kompozycyjna nie jest pomijana i powoduje to problem z wydajnością, sprawdź, czy przy każdym przejściu są tworzone nowe instancje niestabilnych parametrów lub czy zamiast
Stateużywane są właściwościvar. - Raporty kompilatora: możesz używać raportów kompilatora, aby określić, jaka stabilność jest wnioskowana o Twoich klasach.
- Kolekcje: Compose uznaje standardowe interfejsy kolekcji (
List,SetiMap) za niestabilne, ponieważ ich podstawowe implementacje mogą być zmienne. W trybie silnego pomijania (domyślnie włączonym w Kotlin 2.0.20+) niestabilne kolekcje nadal pomijają rekompozycję, używając równości instancji (===), gdy odwołanie do kolekcji się nie zmienia. Zachowaj ostrożność, gdy zastępujesz standardowe kolekcje kolekcjami niezmiennymi Kotlinx lub dodajesz do klas zawierających kolekcje adnotacje@Immutablelub@Stable, ponieważ porównywanie każdego elementu z.equals()przy każdym przejściu rekompozycji może być bardziej kosztowne niż sama rekompozycja. - Inne moduły: Compose uznaje klasy z modułów, w których kompilator Compose nie działa, za niestabilne (porównywane za pomocą
===w trybie silnego pomijania). Jeśli źródło danych często ponownie tworzy instancje małych, płaskich modeli z modułów innych niż Compose z identycznymi wartościami, możesz skonfigurować plik konfiguracji stabilności lub użyć@Stablelub@Immutable, gdzie.equals()jest tanie.
Więcej informacji
- Wydajność: więcej wskazówek dotyczących debugowania wydajności Compose znajdziesz w przewodniku po sprawdzonych metodach i prelekcji z I/O.