Diagnozowanie problemów ze stabilnością

Jeśli występują problemy z wydajnością wynikające z niepotrzebnej lub nadmiernej rekompozycji, należy debugować stabilność aplikacji. W tym przewodniku opisano kilka metod, które można zastosować.

Layout Inspector

Narzędzie Layout Inspector w Android Studio pozwala sprawdzić, które elementy kompozycyjne są rekomponowane w aplikacji. Wyświetla liczbę rekompozycji lub pominięć komponentu przez Compose.

Liczba rekompozycji i pominięć w narzędziu Layout Inspector

Raporty kompilatora Compose

Kompilator Compose może wyświetlać wyniki wnioskowania o stabilności do sprawdzenia. Na podstawie tych danych wyjściowych możesz określić, które elementy kompozycyjne można pominąć, a których nie. W poniższych podsekcjach opisujemy, jak korzystać z tych raportów. Więcej informacji znajdziesz w dokumentacji technicznej.

Konfiguracja

Raporty kompilatora Compose nie są domyślnie włączone. Możesz je aktywować za pomocą flagi kompilatora. Dokładna konfiguracja zależy od projektu, ale w przypadku projektów korzystających z wtyczki Gradle kompilatora Compose możesz dodać te elementy w pliku build.gradle każdego modułu.

  android { ... }

  composeCompiler {
    reportsDestination = layout.buildDirectory.dir("compose_compiler")
    metricsDestination = layout.buildDirectory.dir("compose_compiler")
  }

Raporty kompilatora Compose będą teraz generowane podczas tworzenia projektu.

Przykładowe dane wyjściowe

reportsDestination generuje 3 pliki. Poniżej przedstawiono przykładowe dane wyjściowe z aplikacji JetSnack.

  • <modulename>-classes.txt:raport o stabilności klas w tym module. Przykładowe.
  • <modulename>-composables.txt: raport o tym, czy funkcje kompozycyjne w module można ponownie uruchamiać i pomijać. Przykładowe.
  • <modulename>-composables.csv: wersja raportu o funkcjach kompozycyjnych w formacie CSV, którą można zaimportować do arkusza kalkulacyjnego lub przetworzyć za pomocą skryptu. Próbka

Raport dotyczący funkcji kompozycyjnych

Plik composables.txt zawiera szczegółowe informacje o każdej funkcji typu „composable” w danym module, w tym o stabilności jej parametrów oraz o tym, czy można ją ponownie uruchomić lub pominąć. Oto hipotetyczny przykład z aplikacji JetSnack:

restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun SnackCollection(
  stable snackCollection: SnackCollection
  stable onSnackClick: Function1<Long, Unit>
  stable modifier: Modifier? = @static Companion
  stable index: Int = @static 0
  stable highlight: Boolean = @static true
)

W tym przykładzie każdy parametr SnackCollection jest oznaczony jako stable, więc Compose porównuje każdy parametr za pomocą równości strukturalnej (Object.equals()). Parametry stable idealnie nadają się do wartości podstawowych i małych, płaskich klas danych z tanimi kontrolami .equals(), ale wymuszanie, aby modele zawierające kolekcje (takie jak SnackCollection, który opakowuje List<Snack>) były stable, powoduje, że Compose wywołuje .equals() dla każdego elementu przy każdej rekompozycji. Gdy włączony jest tryb silnego pomijania, lepiej jest zachować modele, które zawierają kolekcje unstable, aby Compose porównywał je za pomocą równości instancji (===).

Możesz też rozważyć inny przykład wygenerowany przy wyłączonym trybie silnego pomijania:

restartable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
  stable index: Int
  unstable snacks: List<Snack>
  stable onSnackClick: Function1<Long, Unit>
  stable modifier: Modifier? = @static Companion
)

Gdy tryb silnego pomijania jest wyłączony (przed Kotlinem 2.0.20), funkcji kompozycyjnej HighlightedSnacks nie można pominąć – Compose nigdy nie pomija jej podczas rekompozycji, nawet jeśli żaden z jej parametrów nie uległ zmianie, ze względu na parametr unstable snacks.

Gdy włączony jest tryb silnego pomijania (domyślny w Kotlin 2.0.20 i nowszych), funkcja HighlightedSnacks jest oznaczona jako skippable, a Compose porównuje parametr unstable snacks, używając równości instancji (===). W efekcie Compose pomija funkcję HighlightedSnacks, jeśli przekazano tę samą referencję List, i ponownie ją komponuje tylko wtedy, gdy przekazano nowo przydzieloną instancję List.

Raport Zajęcia

Plik classes.txt zawiera podobny raport dotyczący klas w danym module. Poniższy fragment kodu to dane wyjściowe klasy Snack:

unstable class Snack {
  stable val id: Long
  stable val name: String
  stable val imageUrl: String
  stable val price: Long
  stable val tagline: String
  unstable val tags: Set<String>
  <runtime stability> = Unstable
}

Dla przypomnienia – oto definicja Snack:

data class Snack(
    val id: Long,
    val name: String,
    val imageUrl: String,
    val price: Long,
    val tagline: String = "",
    val tags: Set<String> = emptySet()
)

Kompilator Compose oznaczył Snack jako niestabilny. Dzieje się tak, ponieważ typ parametru tags to Set<String>. Jest to typ niezmienny, ponieważ nie jest to MutableSet. Jednak standardowe klasy kolekcji, takie jak Set, List i Map, są ostatecznie interfejsami. Dlatego podstawowa implementacja może być nadal zmienna.

Możesz na przykład napisać val set: Set<String> = mutableSetOf("foo"). Zmienna jest stała, a jej zadeklarowany typ nie jest zmienny, ale jej implementacja nadal jest zmienna. Kompilator Compose nie może mieć pewności co do niezmienności tej klasy, ponieważ widzi tylko zadeklarowany typ. Dlatego oznacza tags jako niestabilny.