Na tej stronie dowiesz się więcej o cyklu życia funkcji kompozycyjnej oraz o tym, jak Compose decyduje, czy funkcja kompozycyjna wymaga rekompozycji.
Omówienie cyklu życia
Jak wspomnieliśmy w dokumentacji Zarządzanie stanem, kompozycja opisuje interfejs aplikacji i jest tworzona przez uruchamianie funkcji typu „composable”. Kompozycja to struktura drzewiasta funkcji typu „composable”, które opisują interfejs.
Gdy Jetpack Compose po raz pierwszy uruchamia funkcje typu „composable” podczas początkowej kompozycji, śledzi funkcje typu „composable”, które wywołujesz, aby opisać interfejs w kompozycji. Następnie, gdy stan aplikacji się zmieni, Jetpack Compose zaplanuje rekompozycję. Ponowne utworzenie to sytuacja, w której Jetpack Compose ponownie wykonuje funkcje typu „composable”, które mogły się zmienić w odpowiedzi na zmiany stanu, a następnie aktualizuje kompozycję, aby odzwierciedlić te zmiany.
Kompozycję można utworzyć tylko za pomocą początkowej kompozycji, a zaktualizować za pomocą ponownego utworzenia. Jedynym sposobem na zmodyfikowanie kompozycji jest ponowne utworzenie.
Rekompozycja jest zwykle wywoływana przez zmianę obiektu
State<T>. Compose
śledzi te zmiany i uruchamia wszystkie funkcje typu „composable” w kompozycji, które odczytują ten
konkretny State<T>, oraz wszystkie funkcje typu „composable”, które wywołują i których nie można
pominąć.
Jeśli funkcja typu „composable” jest wywoływana wiele razy, w kompozycji umieszczanych jest wiele jej instancji. Każde wywołanie ma własny cykl życia w kompozycji.
@Composable fun MyComposable() { Column { Text("Hello") Text("World") } }
MyComposable w kompozycji. Jeśli funkcja typu „composable” jest wywoływana wiele razy, w kompozycji umieszczanych jest wiele jej instancji. Element o innym kolorze oznacza, że jest to osobna instancja.Anatomia funkcji typu „composable” w kompozycji
Instancja funkcji typu „composable” w kompozycji jest identyfikowana przez miejsce wywołania. Kompilator Compose traktuje każde miejsce wywołania jako odrębne. Wywoływanie funkcji typu „composable” z wielu miejsc wywołania spowoduje utworzenie w kompozycji wielu instancji tej funkcji.
Jeśli podczas ponownego utworzenia funkcja typu „composable” wywołuje inne funkcje typu „composable” niż podczas poprzedniej kompozycji, Compose określi, które funkcje typu „composable” zostały wywołane, a które nie, a w przypadku funkcji typu „composable”, które zostały wywołane w obu kompozycjach, pominie ponowne utworzenie, jeśli ich dane wejściowe się nie zmieniły.
Zachowanie tożsamości jest kluczowe, aby powiązać efekty uboczne z funkcją typu „composable”, tak aby mogły się one zakończyć pomyślnie, a nie restartować przy każdym ponownym utworzeniu.
Przyjrzyj się temu przykładowi:
@Composable fun LoginScreen(showError: Boolean) { if (showError) { LoginError() } LoginInput() // This call site affects where LoginInput is placed in Composition } @Composable fun LoginInput() { /* ... */ } @Composable fun LoginError() { /* ... */ }
W powyższym fragmencie kodu funkcja LoginScreen warunkowo wywołuje funkcję typu „composable” LoginError i zawsze wywołuje funkcję typu „composable” LoginInput. Każde wywołanie ma unikalne miejsce wywołania i pozycję źródłową, których kompilator używa do jego jednoznacznego zidentyfikowania.
LoginScreen w kompozycji, gdy stan się zmienia i następuje ponowne utworzenie. Ten sam kolor oznacza, że funkcja nie została ponownie utworzona.Mimo że funkcja LoginInput była najpierw wywoływana jako pierwsza, a potem jako druga,
jej instancja LoginInput zostanie zachowana podczas ponownego utworzenia. Dodatkowo, ponieważ funkcja LoginInput nie ma żadnych parametrów, które zmieniły się podczas rekompozycji, wywołanie funkcji LoginInput zostanie pominięte przez Compose.
Dodawanie dodatkowych informacji, które pomagają w inteligentnym ponownym tworzeniu
Wywołanie funkcji typu „composable” wiele razy spowoduje dodanie jej do kompozycji również wiele razy. Gdy funkcja typu „composable” jest wywoływana wiele razy z tego samego miejsca wywołania, Compose nie ma informacji, które pozwoliłyby jednoznacznie zidentyfikować każde wywołanie tej funkcji. Dlatego oprócz miejsca wywołania używana jest kolejność wykonywania, aby zachować odrębność instancji. To zachowanie czasami wystarcza, ale w niektórych przypadkach może powodować niepożądane działanie.
@Composable fun MoviesScreen(movies: List<Movie>) { Column { for (movie in movies) { // MovieOverview composables are placed in Composition given its // index position in the for loop MovieOverview(movie) } } }
W powyższym przykładzie Compose używa kolejności wykonywania oprócz miejsca wywołania, aby zachować odrębność instancji w kompozycji. Jeśli nowy movie zostanie dodany
na dół listy, Compose może ponownie użyć instancji, które są już w
kompozycji, ponieważ ich położenie na liście się nie zmieniło, a więc dane wejściowe
movie są takie same w przypadku tych instancji.
MoviesScreen w kompozycji, gdy nowy element zostanie dodany na dół listy. Funkcje typu „composable” MovieOverview w kompozycji można ponownie wykorzystać. Ten sam kolor w MovieOverview oznacza, że funkcja typu „composable” nie została ponownie utworzona.Jeśli jednak lista movies zmieni się przez dodanie elementu na górę lub
środek listy, usunięcie lub zmianę kolejności elementów, spowoduje to ponowne utworzenie
wszystkich wywołań MovieOverview, których parametr wejściowy zmienił pozycję na
liście. Jest to niezwykle ważne, jeśli np. funkcja MovieOverview pobiera obraz filmu za pomocą efektu ubocznego. Jeśli rekompozycja nastąpi podczas wykonywania efektu, zostanie on anulowany i uruchomi się ponownie.
@Composable fun MovieOverview(movie: Movie) { Column { // Side effect explained later in the docs. If MovieOverview // recomposes, while fetching the image is in progress, // it is cancelled and restarted. val image = loadNetworkImage(movie.url) MovieHeader(image) /* ... */ } }
MoviesScreen w kompozycji, gdy nowy element zostanie dodany do listy. Funkcji typu „composable” MovieOverview nie można ponownie użyć, a wszystkie efekty uboczne zostaną zrestartowane. Inny kolor w MovieOverview oznacza, że funkcja typu „composable” została ponownie utworzona.Najlepiej byłoby, gdyby tożsamość instancji MovieOverview była
powiązana z tożsamością przekazywanego do niej movie. Jeśli zmienimy kolejność listy filmów, najlepiej byłoby, gdybyśmy w podobny sposób zmienili kolejność instancji w drzewie kompozycji, zamiast ponownie tworzyć każdą funkcję typu „composable” MovieOverview z inną instancją filmu. Compose umożliwia określenie, jakich wartości chcesz używać do identyfikowania danej części drzewa: key
funkcji typu „composable”.
Jeśli otoczysz blok kodu wywołaniem funkcji typu „composable” key z przekazanymi co najmniej 2 wartościami, te wartości zostaną połączone i użyte do zidentyfikowania tej instancji w kompozycji. Wartość key nie musi być globalnie unikalna, ale musi być unikalna wśród wywołań funkcji typu „composable” w miejscu wywołania. W tym przykładzie każdy movie musi mieć
key, który jest unikalny wśród movies. Nie ma problemu, jeśli ten key jest współdzielony z
inną funkcją typu „composable” w innej części aplikacji.
@Composable fun MoviesScreenWithKey(movies: List<Movie>) { Column { for (movie in movies) { key(movie.id) { // Unique ID for this movie MovieOverview(movie) } } } }
Dzięki temu nawet jeśli elementy na liście się zmienią, Compose rozpoznaje poszczególne wywołania MovieOverview i może ich ponownie użyć.
MoviesScreen w kompozycji, gdy nowy element zostanie dodany do listy. Ponieważ funkcje typu „composable” MovieOverview mają unikalne klucze, Compose rozpoznaje, które instancje MovieOverview się nie zmieniły, i może ich ponownie użyć. Ich efekty uboczne będą nadal wykonywane.Niektóre funkcje typu „composable” mają wbudowaną obsługę funkcji typu „composable” key. Na przykład LazyColumn umożliwia określenie niestandardowego key w DSL items.
@Composable fun MoviesScreenLazy(movies: List<Movie>) { LazyColumn { items(movies, key = { movie -> movie.id }) { movie -> MovieOverview(movie) } } }
Pomijanie, jeśli dane wejściowe się nie zmieniły
Podczas ponownego utworzenia wykonanie niektórych kwalifikujących się funkcji typu „composable” można całkowicie pominąć, jeśli ich dane wejściowe nie zmieniły się od czasu poprzedniej kompozycji.
Funkcja typu „composable” kwalifikuje się do pominięcia chyba że:
- funkcja ma typ zwracany inny niż
Unit. - funkcja jest oznaczona adnotacją
@NonRestartableComposablelub@NonSkippableComposable. - wymagany parametr jest typu niestabilnego.
Istnieje tryb kompilatora, Strong Skipping, który łagodzi ostatni wymóg.
Aby typ był uznawany za stabilny, musi spełniać te warunki:
- Wynik
equalsdla 2 instancji będzie zawsze taki sam w przypadku tych samych 2 instancji. - Jeśli zmieni się publiczna właściwość typu, kompozycja zostanie o tym powiadomiona.
- Wszystkie typy właściwości publicznych są również stabilne.
Istnieją ważne typy, które spełniają te warunki i które kompilator Compose będzie traktować jako stabilne, mimo że nie są one wyraźnie oznaczone jako stabilne za pomocą adnotacji @Stable:
- Wszystkie typy wartości pierwotnych:
Boolean,Int,Long,Float,Charitp. - Ciągi znaków
- Wszystkie typy funkcji (lambdy)
Wszystkie te typy mogą spełniać warunki stabilności, ponieważ są niezmienne. Ponieważ typy niezmienne nigdy się nie zmieniają, nie muszą powiadamiać kompozycji o zmianie, więc łatwiej jest spełnić te warunki.
Jednym z typów, który jest stabilny, ale jest zmienny, jest typ MutableState w Compose. Jeśli wartość jest przechowywana w MutableState, obiekt stanu jest ogólnie
uważany za stabilny, ponieważ Compose będzie powiadamiany o wszelkich zmianach właściwości
.value w State.
Gdy wszystkie typy przekazywane jako parametry do funkcji typu „composable” są stabilne, wartości parametrów są porównywane pod kątem równości na podstawie pozycji funkcji typu „composable” w drzewie interfejsu. Rekompozycja jest pomijana, jeśli wszystkie wartości są niezmienione od czasu poprzedniego wywołania.
Compose uważa typ za stabilny tylko wtedy, gdy może to udowodnić. Na przykład interfejs jest zwykle traktowany jako niestabilny, a typy ze zmiennymi właściwościami publicznymi, których implementacja może być niezmienna, również nie są stabilne.
Jeśli Compose nie może stwierdzić, że typ jest stabilny, ale chcesz wymusić na Compose traktowanie go jako stabilnego, oznacz go adnotacją
@Stable.
// Marking the type as stable to favor skipping and smart recompositions. @Stable interface UiState<T : Result<T>> { val value: T? val exception: Throwable? val hasError: Boolean get() = exception != null }
W powyższym fragmencie kodu, ponieważ UiState jest interfejsem, Compose może zwykle uznać ten typ za niestabilny. Dodając adnotację @Stable, informujesz Compose, że ten typ jest stabilny, co pozwala Compose na preferowanie inteligentnego ponownego tworzenia. Oznacza to również, że jeśli interfejs jest używany jako typ parametru, Compose będzie traktować wszystkie jego implementacje jako stabilne.
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy język JavaScript jest wyłączony.
- Stan i Jetpack Compose
- Efekty uboczne w Compose
- Zapisywanie stanu interfejsu w Compose