Stabilność tworzenia wiadomości

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 ponownymi kompozycjami. Typ jest niestabilny, jeśli Compose nie może określić, czy jego wartość zmieniła się między ponownymi kompozycjami.

Compose używa stabilności parametrów funkcji kompozycyjnej, aby określić, jak porównywać dane wejściowe i czy może pominąć funkcję kompozycyjną podczas ponownej kompozycji (z domyślnie włączonym trybem silnego pomijania od wersji Kotlin 2.0.20):

  • Stabilne parametry: Compose porównuje stabilne parametry za pomocą równości strukturalnej (Object.equals()). Jeśli stabilne parametry elementu kompozycyjnego są równe ich poprzednim wartościom, Compose pomija ten element.
  • Niestabilne parametry: gdy włączony jest tryb silnego pomijania (domyślny w Kotlinie w wersji 2.0.20 i nowszych), Compose porównuje niestabilne parametry 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) funkcja 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.equals() sprawdzanie dużych kolekcji przez nadmierne dodawanie adnotacji do modeli, możesz zauważyć niepotrzebne ponowne kompozycje lub narzut związany z porównywaniem.

W tym dokumencie 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 ponownego komponowania.

Klasa Contact to niezmienialna klasa danych. Dzieje się tak, ponieważ wszystkie jego parametry są typami prostymi 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łącznika i zmieni się stan selected:

  1. Compose sprawdza, czy należy ponownie skompilować kod w ContactRow.
  2. Widzi, że jedyny argument dla ContactDetails jest typu Contact.
  3. Ponieważ Contact to niezmienna klasa danych, Compose ma pewność, że żaden z argumentów funkcji ContactDetails nie uległ zmianie.
  4. Dlatego funkcja tworzenia pomija ContactDetails i nie tworzy go ponownie.
  5. Z drugiej strony argumenty funkcji ToggleButton uległy zmianie, a Compose ponownie komponuje ten komponent.

Obiekty modyfikowalne

W przykładzie powyżej 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 stanu Compose.

Compose uznaje taką klasę za niestabilną. W trybie silnego pomijania (Kotlin 2.0.20+) Compose porównuje Contact za pomocą równości instancji (===): zmiana contact.name w miejscu nie spowoduje ponownego komponowania, a przekazanie nowo przydzielonej instancji Contact o identycznych wartościach nadal wymusi ponowne komponowanie ContactDetails (a bez trybu silnego pomijania ContactDetails jest ponownie komponowany za każdym razem, gdy zmieni się selected).

Implementacja w Compose

Warto, choć nie jest to konieczne, zastanowić się, w jaki sposób Compose określa, które funkcje pominąć podczas ponownego komponowania.

Gdy kompilator Compose uruchamia się w Twoim kodzie, oznacza każdą funkcję i każdy 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 funkcja może być oznaczona jako jedna z tych kategorii, obie lub żadna z nich:

  • Możliwość pominięcia: jeśli kompilator oznaczy funkcję kompozycyjną jako możliwą do pominięcia, Compose może pominąć ją podczas ponownej kompozycji, jeśli wszystkie jej argumenty są równe poprzednim wartościom. (W przypadku trybu silnego pomijania w Kotlinie w wersji 2.0.20 lub nowszej wszystkie kompozycje, które można ponownie uruchomić, są automatycznie pomijalne).
  • Możliwość ponownego uruchomienia: funkcja kompozycyjna, którą można ponownie uruchomić, służy jako „zakres”, w którym może się rozpocząć rekompozycja. Innymi słowy, funkcja może być punktem wejścia, w którym Compose może ponownie zacząć wykonywać kod w celu ponownego komponowania po zmianach stanu.

Typy

Typy kompozycji są niezmienne lub stabilne. Każdy typ jest jednym z tych dwóch:

  • Niezmienny: Compose oznacza typ jako niezmienny, jeśli wartość jego właściwości nigdy się nie zmienia, a wszystkie metody są referencyjnie przejrzyste.
    • Pamiętaj, że wszystkie typy proste są oznaczone jako niezmienne. Są to m.in. String, Int i Float.
  • Stabilny: oznacza 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 programu, Compose wykryje te zmiany.

Stabilność debugowania

Jeśli aplikacja ponownie komponuje funkcję kompozycyjną, której parametry nie uległy zmianie, najpierw sprawdź, czy podczas każdej ponownej kompozycji przydzielane są nowe instancje niestabilnych typów (lub, jeśli tryb silnego pomijania jest wyłączony, czy funkcja kompozycyjna 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

Pamiętaj o tych kwestiach:

  • Parametry: funkcja Compose określa stabilność każdego parametru funkcji kompozycyjnych, aby zdecydować, czy podczas ponownej kompozycji porównywać je za pomocą równości strukturalnej (.equals()) czy równości instancji (===).
  • Natychmiastowe poprawki: jeśli zauważysz, że funkcja kompozycyjna nie jest pomijana i powoduje problem z wydajnością, sprawdź, czy nowe instancje niestabilnych parametrów są odtwarzane przy każdym przebiegu, czy też zamiast State używane są właściwości var.
  • Raporty kompilatora: możesz używać raportów kompilatora, aby określić, jaka stabilność jest wnioskowana w przypadku Twoich klas.
  • Kolekcje: interfejsy standardowych kolekcji (List, Set i Map) są w Compose uznawane za niestabilne, ponieważ ich implementacje mogą być zmienne. W trybie silnego pomijania (domyślnie włączonym w Kotlinie w wersji 2.0.20 lub nowszej) niestabilne kolekcje nadal pomijają ponowne komponowanie, korzystając z szybkiego porównywania instancji O(1) (===), gdy odwołanie do kolekcji nie ulega zmianie. Unikaj konwertowania dużych kolekcji na niezmienne kolekcje Kotlinx lub oznaczania klas zawierających kolekcje adnotacjami @Immutable lub @Stable, chyba że wymagana jest równość strukturalna (O(N) .equals()), ponieważ porównywanie każdego elementu przy każdej ponownej kompozycji może być droższe niż sama ponowna kompozycja.
  • Inne moduły: kompilator Compose uznaje klasy z modułów, w których nie działa kompilator Compose, za niestabilne (porównanie z użyciem === w trybie silnego pomijania). Jeśli źródło danych często ponownie tworzy małe, płaskie modele z modułów innych niż Compose o identycznych wartościach, możesz skonfigurować plik konfiguracji stabilności lub użyć @Stable lub @Immutable, gdzie .equals() jest tanie.

Więcej informacji