Cykl życia elementów kompozycyjnych

Na tej stronie dowiesz się więcej o cyklu życia komponentu oraz o tym, jak Compose decyduje, czy komponent wymaga ponownego skompilowania.

Przegląd cyklu życia

Jak wspomnieliśmy w dokumentacji dotyczącej zarządzania stanem, Kompozycja opisuje interfejs aplikacji i jest tworzona przez uruchomienie elementów kompozycyjnych. Kompozycja to struktura drzewikowa komponentów, które opisują Twój interfejs.

Gdy Jetpack Compose po raz pierwszy uruchomi kompozycje podczas początkowej operacji kompozycji, będzie śledzić elementy kompozycyjne, które wywołujesz w celu opisania i interfejs w kompozycji. Następnie, gdy zmieni się stan aplikacji, Jetpack Tworzenie powoduje zaplanowanie zmiany kompozycji. Rekompozycja to sytuacja, w której Jetpack Compose ponownie wykonuje komponenty, które mogły ulec zmianie w odpowiedzi na zmiany stanu, a potem aktualizuje kompozycję, aby odzwierciedlić wszelkie zmiany.

Kompozycja może być wygenerowana tylko przez początkową kompozycję i zaktualizowana przez ponowne złożenie. Jedynym sposobem na zmodyfikowanie kompozycji jest jej ponowne kompozycje.

Diagram przedstawiający cykl życia elementu kompozycyjnego

Rysunek 1. Cykl życia komponenta w kompozycji. Trafia ona do Kompozycja, zostaje przekomponowana co najmniej 0 razy i opuszcza kompozycję.

Rekompozycja jest zwykle wywoływana przez zmianę obiektu State<T>. Utwórz je śledzi i uruchamia wszystkie kompozycje w ramach kompozycji, które konkretnego elementu State<T> oraz wszelkich elementów kompozycyjnych wywoływanych przez te funkcje, których nie można pominięte.

Jeśli funkcja kompozycyjna zostanie wywołana wiele razy, w funkcji Kompozycja. Każde wywołanie ma własny cykl życia w kompozycji.

@Composable
fun MyComposable() {
    Column {
        Text("Hello")
        Text("World")
    }
}

Diagram pokazujący hierarchiczny układ elementów w poprzednim fragmencie kodu

Rysunek 2. Przykład MyComposable w kompozycji. Jeśli funkcja kompozycyjna jest wywoływana wiele razy, wiele wystąpień jest umieszczonych w funkcji Kompozycja. Jeśli element ma inny kolor, oznacza to, że jest osobną instancję.

Struktura komponentu w komponentach

Wystąpienie kompozytowalnej funkcji w kompozycji jest identyfikowane przez jej miejsce wywołania. Kompilator Compose uznaje każdą witrynę generującą połączenia za osobną. Nawiązywanie połączenia z elementami kompozycyjnymi z wielu witryn generujących połączenia utworzy wiele instancji elementu kompozycyjnego w Kompozycja.

Jeśli podczas ponownej kompozycji funkcja kompozycyjna wywołuje inne elementy kompozycyjne niż w rzeczywistości. w trakcie poprzedniej kompozycji, funkcja Compose wskazuje, które kompozycje lub nie, a także dla elementów kompozycyjnych, które zostały wywołane w obu kompozycje, funkcja Kompozycje unika ich ponownego komponowania, jeśli dane wejściowe nie .

Zachowanie tożsamości ma kluczowe znaczenie dla powiązania efektów ubocznych z treściami kompozycyjnymi, dzięki czemu mogą ukończyć proces, a nie uruchamiać go ponownie zmianę kompozycji.

Przyjrzyjmy 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() { /* ... */ }

We fragmencie kodu powyżej funkcja LoginScreen warunkowo wywoła funkcję LoginError kompozycyjny i zawsze wywołuje funkcję LoginInput kompozycyjną. Każdy ma unikalną witrynę wywołującą i pozycję źródłową, których kompilator używa jednoznacznie identyfikować użytkowników.

Diagram pokazujący, jak poprzedni kod jest ponownie złożony, jeśli flaga showError zostanie ustawiona na wartość true. Dodano komponent LoginError, ale inne komponenty nie zostały ponownie złożone.

Rysunek 3. Reprezentacja LoginScreen w kompozycji, gdy stan się zmienia i występuje ponowne tworzenie kompozycji. Ten sam kolor oznacza, że nie została ona ponownie złożona.

Mimo że funkcja LoginInput została wywołana jako pierwsza, a potem jako druga, instancja LoginInput będzie zachowana w przypadku każdej rekompozycji. Dodatkowo: ponieważ LoginInput nie ma żadnych parametrów, które zmieniły się w przekomponowanie, wywołanie LoginInput zostanie pominięte przez funkcję Utwórz.

Dodawanie dodatkowych informacji, które ułatwiają inteligentne zmiany składu reklam

Wywołanie funkcji kompozycyjnej kilka razy spowoduje dodanie jej do kompozycji wielokrotnie jako cóż. W przypadku wielokrotnego wywoływania funkcji kompozycyjnej z tej samej witryny połączenia, Utwórz nie ma żadnych informacji, które pozwoliłyby jednoznacznie zidentyfikować każde wywołanie tego elementu kompozycyjnego, w celu zachowania konstrukcji zamówienia oprócz witryny wywołującej w różnych instancjach. Czasami to wystarczy, ale czasami może powodować niepożądane zachowanie.

@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 funkcja Utwórz oprócz wywołania w celu zachowania odrębności instancji w obrębie Kompozycji. Jeśli dodano nowe pole movie na dół listy, funkcja Utwórz może ponownie użyć instancji znajdujących się już Od momentu, w którym ich położenie na liście się nie zmieniło, Dane wejściowe funkcji movie są takie same w przypadku tych instancji.

Schemat przedstawiający ponowne komponowanie poprzedniego kodu w przypadku dodania nowego elementu u dołu listy. Pozostałe elementy na liście nie zmieniły pozycji i nie zostały zmienione.

Rysunek 4. Przykład elementu MoviesScreen w kompozycji, gdy nowy element jest dodawany na końcu listy. Komponenty MovieOverview w kompozycji można ponownie wykorzystać. Ten sam kolor w komponowalnym elemencie MovieOverview oznacza, że nie został on ponownie skompilowany.

Jeśli jednak lista movies ulegnie zmianie, np. przez dodanie elementów do góry lub pośredniej pozycji na liście, usunięcie elementów lub zmianę ich kolejności, spowoduje to ponowne ułożenie wszystkich wywołań 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 ponowne kompozycje mają miejsce, gdy efekt jest aktywny postęp zostanie anulowany i rozpocznie się od nowa.

@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)

        /* ... */
    }
}

Diagram pokazujący, jak poprzedni kod jest ponownie składany, gdy nowy element zostanie dodany na szczyt listy. Wszystkie inne elementy na liście zmieniają pozycję i muszą zostać ponownie skomponowane.

Rysunek 5. Przykładowy element MoviesScreen w kompozycji, gdy do listy dodano nowy element. Komponentów MovieOverview nie można ponownie wykorzystać, a wszystkie efekty uboczne zostaną ponownie uruchomione. Inny kolor w MovieOverview oznacza, że usługa została ponownie skompilowana.

Najlepiej myśleć o tożsamości instancji MovieOverview jako: powiązane z tożsamością movie, która jest do niej przekazywana. Jeśli zmienimy kolejność listę filmów, w miarę możliwości uporządkowalibyśmy w podobny sposób wystąpienia drzewo kompozycji zamiast ponownie komponować każdy element MovieOverview z funkcją w innym miejscu. Dzięki funkcji Compose możesz określić w czasie wykonywania, których wartości chcesz używać do identyfikowania danej części drzewa: kompozyt key.

Opakowując blok kodu za pomocą wywołania klucza „composable” z co najmniej jednym przekazywane wartości, zostaną one połączone i będą używane do identyfikacji w scenie kompozycji. Wartość pola key nie musi wynosić jest unikalny globalnie, musi być niepowtarzalny tylko wśród wywołań funkcji w centrum obsługi rozmów. Zatem w tym przykładzie każdy element movie musi mieć key, który jest unikalny wśród: movies; nie ma nic złego w tym, że key ma dostęp i inne elementy kompozycyjne w innym miejscu w aplikacji.

@Composable
fun MoviesScreenWithKey(movies: List<Movie>) {
    Column {
        for (movie in movies) {
            key(movie.id) { // Unique ID for this movie
                MovieOverview(movie)
            }
        }
    }
}

Nawet jeśli elementy na liście ulegną zmianie, funkcja Utwórz rozpozna poszczególnych połączeń z MovieOverview i możesz ich używać ponownie.

Diagram pokazujący, jak poprzedni kod jest ponownie składany, gdy nowy element zostanie dodany na szczyt listy. Elementy listy są identyfikowane za pomocą kluczy, dlatego funkcja tworzenia wiadomości wie, że nie ma ich ponownie komponować, nawet jeśli ich pozycje się zmieniły.

Rysunek 6. Reprezentacja elementu MoviesScreen w kompozycjach, gdy nowy element jest dodawany do listy. Ponieważ elementy kompozycyjne MovieOverview mają unikalne atrybuty kluczy, funkcja tworzenia wykrywa, które MovieOverview instancje nie uległy zmianie, oraz nie mogą ich ponownie wykorzystywać; efekty uboczne będą nadal realizowane.

Niektóre funkcje kompozycyjne mają wbudowaną obsługę funkcji kompozycyjnej key. Przykład: LazyColumn akceptuje określenie niestandardowego elementu key w usłudze DSL items.

@Composable
fun MoviesScreenLazy(movies: List<Movie>) {
    LazyColumn {
        items(movies, key = { movie -> movie.id }) { movie ->
            MovieOverview(movie)
        }
    }
}

Pomijam je, jeśli dane wejściowe się nie zmieniły

Podczas zmiany kompozycji niektóre dostępne funkcje kompozycyjne mogą mieć własne atrybuty wykonanie zostało całkowicie pominięte, jeśli dane wejściowe nie zmieniły się w porównaniu z poprzednim kompozycji.

Funkcja typu „composable” może być pomijana chyba że:

  • Funkcja zwraca typ inny niż Unit.
  • Funkcja jest oznaczona adnotacją @NonRestartableComposable lub @NonSkippableComposable
  • Wymagany parametr ma niestabilny typ

Dostępny jest eksperymentalny tryb kompilatora, Strong Skipping, który łagodzi ostatnie wymaganie.

Aby typ został uznany za stabilny, musi spełniać te wymagania: umowa:

  • Wynik funkcji equals dla 2 wystąpienia będzie zawsze taki sam dla tych samych 2 wystąpienia.
  • Jeśli zmieni się właściwość publiczna danego typu, zostanie powiadomiona o tym kompozycja.
  • Wszystkie publiczne typy usług są też stabilne.

Istnieje kilka ważnych, typowych typów, które wliczają się do tej umowy, Kompilator do tworzenia wiadomości będzie traktowany jako stabilny, choć nie jest wyraźnie oznaczone jako stabilne za pomocą adnotacji @Stable:

  • Wszystkie typy wartości podstawowych: Boolean, Int, Long, Float, Char itp.
  • Strings
  • Wszystkie typy funkcji (lambda)

Wszystkie te typy mogą przestrzegać umowy stabilnej, ponieważ są niezmienne. Typy niezmienne nigdy się nie zmieniają, więc nie trzeba powiadamiać Composition o zmianach, co znacznie ułatwia przestrzeganie tej umowy.

Warto zwrócić uwagę na typ MutableState, który jest stabilny, ale może ulec zmianie. Jeśli wartość jest przechowywana w MutableState, obiektem stanu ogólnym jest uznaje się za stabilne, ponieważ funkcja Compose będzie powiadamiana o wszelkich zmianach Właściwość .value elementu State.

Gdy wszystkie typy przekazywane jako parametry funkcji kompozycyjnej są stabilne, parametr są porównywane pod kątem równości na podstawie pozycji kompozycyjnej w interfejsie drzewo. Rekompozycja jest pomijana, jeśli wszystkie wartości są takie same jak w poprzednim wywołaniu.

Compose uznaje typ za stabilny tylko wtedy, gdy może to udowodnić. Na przykład interfejs jest zwykle traktowany jako niestabilny, a typy ze zmiennym publicznym Właściwości, których implementacji można zmienić, także nie są stabilne.

Jeśli Compose nie jest w stanie stwierdzić, że typ jest stabilny, ale chcesz, 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 uważa się ten typ za niestabilny. Dodając adnotację @Stable, informujesz Compose, że ten typ jest stabilny, co pozwala Compose stosować inteligentne przekształcenia. Oznacza to też, że Compose będzie traktować wszystkie swoje implementacje jako stabilne, jeśli interfejs jest używany jako typ parametru.

.