Na tej stronie dowiesz się o cyklu życia funkcji kompozycyjnej i o tym, jak Compose decyduje, czy funkcja kompozycyjna wymaga rekompozycji.
Przegląd cyklu życia
Jak wspomniano w dokumentacji dotyczącej zarządzania stanem, kompozycja opisuje interfejs aplikacji i jest tworzona przez uruchamianie funkcji kompozycyjnych. Kompozycja to struktura drzewa elementów kompozycyjnych, które opisują interfejs.
Gdy Jetpack Compose uruchamia funkcje kompozycyjne po raz pierwszy, podczas początkowego tworzenia kompozycji, śledzi funkcje kompozycyjne, które wywołujesz, aby opisać interfejs w kompozycji. Gdy stan aplikacji się zmieni, Jetpack Compose zaplanuje rekompozycję. Rekonstrukcja ma miejsce, gdy Jetpack Compose ponownie wykonuje funkcje kompozycyjne, które mogły ulec zmianie w odpowiedzi na zmiany stanu, a następnie aktualizuje kompozycję, aby odzwierciedlić te zmiany.
Kompozycję można utworzyć tylko przez początkową kompozycję i zaktualizować przez rekompozycję. Jedynym sposobem na zmodyfikowanie kompozycji jest rekompozycja.
Rekompilacja jest zwykle wywoływana przez zmianę obiektu State<T>. Compose śledzi te zmiany i uruchamia wszystkie funkcje kompozycyjne w kompozycji, które odczytują dany obiekt State<T>, oraz wszystkie wywoływane przez nie funkcje kompozycyjne, których nie można pominąć.
Jeśli funkcja kompozycyjna jest wywoływana wielokrotnie, w kompozycji umieszczanych jest wiele instancji. Każde połączenie ma własny cykl życia w kompozycji.
@Composable fun MyComposable() { Column { Text("Hello") Text("World") } }
MyComposable w kompozycji. Jeśli funkcja kompozycyjna jest wywoływana wielokrotnie, w kompozycji umieszczanych jest wiele instancji. Element o innym kolorze oznacza, że jest to osobna instancja.Anatomia funkcji kompozycyjnej w kompozycji
Instancja funkcji kompozycyjnej w kompozycji jest identyfikowana przez miejsce wywołania. Kompilator Compose traktuje każde miejsce wywołania jako odrębne. Wywołanie funkcji kompozycyjnych z wielu miejsc wywołania spowoduje utworzenie wielu instancji funkcji kompozycyjnej w kompozycji.
Jeśli podczas rekompozycji funkcja kompozycyjna wywołuje inne funkcje kompozycyjne niż podczas poprzedniej kompozycji, Compose określi, które funkcje kompozycyjne zostały wywołane, a które nie. W przypadku funkcji kompozycyjnych, które zostały wywołane w obu kompozycjach, Compose uniknie ich rekompozycji, jeśli ich dane wejściowe nie uległy zmianie.
Zachowanie tożsamości jest kluczowe dla powiązania efektów ubocznych z funkcją kompozycyjną, aby mogły się one zakończyć, a nie uruchamiać ponownie przy każdej rekompozycji.
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 LoginScreen warunkowo wywoła funkcję kompozycyjną LoginError i zawsze wywoła funkcję kompozycyjną LoginInput. Każde wywołanie ma unikalne miejsce wywołania i pozycję źródłową, których kompilator używa do jednoznacznej identyfikacji.
LoginScreen w kompozycji, gdy stan się zmienia i następuje rekompozycja. Ten sam kolor oznacza, że nie został on zmieniony.Mimo że funkcja LoginInput została wywołana jako druga, a nie jako pierwsza, instancja LoginInput zostanie zachowana podczas rekompozycji. 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 pomogą w inteligentnym przekomponowywaniu
Wielokrotne wywołanie funkcji kompozycyjnej spowoduje jej wielokrotne dodanie do kompozycji. Gdy wywołujesz funkcję kompozycyjną wielokrotnie z tego samego miejsca wywołania, Compose nie ma żadnych informacji, które pozwoliłyby jednoznacznie zidentyfikować każde wywołanie tej funkcji, więc oprócz miejsca wywołania używana jest kolejność wykonywania, aby odróżnić instancje. Czasami takie zachowanie jest wystarczające, 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 przykładzie powyżej Compose używa kolejności wykonywania oprócz miejsca wywołania, aby zachować odrębność instancji w kompozycji. Jeśli nowy element movie zostanie dodany na dole listy, Compose może ponownie użyć instancji, które już znajdują się w kompozycji, ponieważ ich lokalizacja na liście nie uległa zmianie, a więc dane wejściowe movie są takie same dla tych instancji.
MoviesScreen w kompozycji, gdy nowy element jest dodawany na dole listy. Komponenty MovieOverview w kompozycji można wykorzystać ponownie. MovieOverviewTen sam kolor w MovieOverview oznacza, że funkcja kompozycyjna nie została ponownie skomponowana.Jeśli jednak lista movies zmieni się przez dodanie elementów na górze lub w środku, usunięcie elementów lub zmianę ich kolejności, spowoduje to rekompozycję we wszystkich wywołaniach MovieOverview, których parametr wejściowy zmienił pozycję na liście. Jest to bardzo ważne, jeśli na przykład MovieOverview pobiera obraz filmu za pomocą efektu ubocznego. Jeśli rekompozycja nastąpi w trakcie działania efektu, zostanie on anulowany i uruchomiony 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 po dodaniu nowego elementu do listy. Nie można ponownie użyć funkcji kompozycyjnych MovieOverview, a wszystkie efekty uboczne zostaną uruchomione ponownie. Inny kolor w MovieOverview oznacza, że element kompozycyjny został ponownie skomponowany.Najlepiej jest traktować tożsamość instancji MovieOverview jako powiązaną z tożsamością przekazywanego do niej elementu movie. Jeśli zmienimy kolejność listy filmów, najlepiej byłoby zmienić kolejność instancji w drzewie kompozycji, zamiast rekomponować każdy element kompozycyjny MovieOverview z inną instancją filmu. Compose umożliwia przekazanie środowisku wykonawczemu informacji o tym, jakich wartości chcesz użyć do zidentyfikowania danej części drzewa: funkcji kompozycyjnej key.
Jeśli opakujesz blok kodu wywołaniem funkcji Key z co najmniej 1 przekazaną wartością, te wartości zostaną połączone i będą używane do identyfikowania tej instancji w kompozycji. Wartość key nie musi być unikalna globalnie, ale musi być unikalna wśród wywołań funkcji kompozycyjnych w miejscu wywołania. W tym przykładzie każdy element movie musi mieć key, który jest unikalny wśród elementów movies. Nie ma znaczenia, czy ten key jest taki sam jak w przypadku innego elementu kompozycyjnego w innym miejscu 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 rozpozna poszczególne wywołania funkcji MovieOverview i będzie mogła ich użyć ponownie.
MoviesScreen w kompozycji po dodaniu nowego elementu do listy. Ponieważ funkcje kompozycyjne MovieOverview mają unikalne klucze, Compose rozpoznaje, które instancje MovieOverview nie uległy zmianie, i może ich użyć ponownie. Ich efekty uboczne będą nadal wykonywane.Niektóre elementy kompozycyjne mają wbudowaną obsługę elementu kompozycyjnego key. Na przykład LazyColumn akceptuje określenie niestandardowego key w języku DSL items.
@Composable fun MoviesScreenLazy(movies: List<Movie>) { LazyColumn { items(movies, key = { movie -> movie.id }) { movie -> MovieOverview(movie) } } }
Pominięcie, jeśli dane wejściowe nie uległy zmianie
Podczas rekompozycji niektóre kwalifikujące się funkcje typu „composable” mogą zostać całkowicie pominięte, jeśli ich dane wejściowe nie zmieniły się od poprzedniej kompozycji.
Funkcję typu „composable” można pominąć, chyba że:
- Funkcja zwraca typ 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 został uznany za stabilny, musi spełniać te warunki:
- Wynik
equalsdla 2 instancji będzie zawsze taki sam dla tych samych 2 instancji. - Jeśli właściwość publiczna typu ulegnie zmianie, Composition zostanie o tym powiadomiony.
- Wszystkie publiczne typy właściwości są również stabilne.
Istnieje kilka ważnych typów, które są zgodne z tą umową i które kompilator Compose traktuje jako stabilne, mimo że nie są one wyraźnie oznaczone jako stabilne za pomocą adnotacji @Stable:
- Wszystkie typy wartości podstawowych:
Boolean,Int,Long,Float,Charitd. - Strings
- Wszystkie typy funkcji (lambdy)
Wszystkie te typy mogą być zgodne z kontraktem stabilności, ponieważ są niezmienne. Typy niezmienne nigdy się nie zmieniają, więc nie muszą powiadamiać funkcji Composition o zmianie, co znacznie ułatwia przestrzeganie tej umowy.
Jednym z ważnych typów, który jest stabilny, ale może być modyfikowalny, 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 obiektu State.
Gdy wszystkie typy przekazywane jako parametry do funkcji kompozycyjnej są stabilne, wartości parametrów są porównywane pod kątem równości na podstawie pozycji funkcji kompozycyjnej w drzewie interfejsu. Rekompilacja jest pomijana, jeśli od poprzedniego wywołania nie zmieniły się żadne wartości.
Compose uznaje typ za stabilny tylko wtedy, gdy może to udowodnić. Na przykład interfejs jest zwykle traktowany jako niestabilny, podobnie jak typy ze zmiennymi właściwościami publicznymi, których implementacja mogłaby być niezmienna.
Jeśli Compose nie może wywnioskować, że typ jest stabilny, ale chcesz wymusić, aby Compose traktował go jako stabilny, 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 }
We fragmencie kodu powyżej UiState jest interfejsem, więc Compose może zwykle uznać ten typ za niestabilny. Dodając adnotację @Stable, informujesz Compose, że ten typ jest stabilny, co pozwala Compose na faworyzowanie inteligentnych rekompozycji. Oznacza to również, że Compose będzie traktować wszystkie implementacje jako stabilne, jeśli interfejs jest używany jako typ parametru.
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy język JavaScript jest wyłączony.
- State i Jetpack Compose
- Efekty uboczne w Compose
- Zapisywanie stanu interfejsu w Compose