Zwiększ wydajność aplikacji dzięki profilom podstawowym

1. Zanim zaczniesz

Z tego ćwiczenia w Codelabs dowiesz się, jak generować profile podstawowe w celu optymalizacji wydajności aplikacji, a także jak zweryfikować korzyści związane z wydajnością korzystania z tych profili.

Czego potrzebujesz

Jakie zadania wykonasz

  • Skonfiguruj projekt, aby używać generatorów profili podstawowych.
  • Wygeneruj profile podstawowe, aby zoptymalizować uruchamianie i przewijanie aplikacji.
  • Sprawdź wzrost wydajności za pomocą biblioteki Jetpack Macrobenchmark.

Czego się nauczysz

  • Profile podstawowe i informacje o tym, jak mogą poprawić wydajność aplikacji.
  • Jak generować profile podstawowe.
  • Wzrost wydajności profili podstawowych.

2. Przygotowanie

Na początek skopiuj repozytorium GitHub z wiersza poleceń, korzystając z następującego polecenia:

$ git clone https://github.com/android/codelab-android-performance.git

Możesz też pobrać 2 pliki ZIP:

Otwórz projekt w Android Studio

  1. W oknie Witamy w Android Studio wybierz 61d0a4432ef6d396.png Otwórz istniejący projekt.
  2. Wybierz folder [Download Location]/codelab-android-performance/baseline-profiles. Wybierz katalog baseline-profiles.
  3. Gdy Android Studio zaimportuje projekt, sprawdź, czy możesz uruchomić moduł app, aby utworzyć przykładową aplikację, z którą później będziesz pracować.

Przykładowa aplikacja

W ramach tego ćwiczenia w Codelabs będziesz pracować z przykładową aplikacją JetSnack. To wirtualna aplikacja do zamawiania przekąsek, która korzysta z Jetpack Compose.

Aby mierzyć wydajność aplikacji, musisz znać strukturę interfejsu użytkownika i sposób działania aplikacji. Pozwoli Ci to uzyskać dostęp do jego elementów w testach porównawczych. Uruchom aplikację i zapoznaj się z podstawowymi ekranami, zamawiając przekąski. Nie musisz znać szczegółów architektury aplikacji.

23633b02ac7ce1bc.png

3. Co to są profile podstawowe

Profile bazowe przyspieszają wykonywanie kodu o około 30% od pierwszego uruchomienia dzięki unikaniu interpretacji i kompilacji w sam raz (JIT) w przypadku uwzględnionych ścieżek kodu. Przesyłając profil podstawowy do aplikacji lub biblioteki, środowisko wykonawcze Androida (ART) może zoptymalizować uwzględnione ścieżki kodu przez kompilację AOT – poprawia wydajność w przypadku każdego nowego użytkownika i przy każdej aktualizacji aplikacji. Optymalizacja oparta na profilu (PGO) pozwala aplikacjom optymalizować uruchamianie, ograniczać bałagan w działaniu i zwiększać ogólną wydajność środowiska wykonawczego w przypadku użytkowników już od pierwszego uruchomienia.

Dzięki profilowi Baseline wszystkie interakcje użytkowników – takie jak uruchamianie aplikacji, poruszanie się między ekranami i przewijanie treści – od pierwszego uruchomienia przebiegają płynniej. Przyspieszenie działania aplikacji i jej responsywności zwiększa liczbę aktywnych użytkowników dziennie oraz wyższy średni współczynnik powrotnych wizyt.

Profile podstawowe pomagają w optymalizacji wykraczającej poza uruchamianie aplikacji, ponieważ zapewniają typowe interakcje użytkowników, które usprawniają działanie aplikacji już od pierwszego uruchomienia. Kompilacja AOT z instrukcjami nie bazuje na urządzeniach użytkowników i można ją wykonać raz na daną wersję na komputerze, na którym programujesz, a nie na urządzeniu mobilnym. Wysyłając wersje za pomocą profilu Baseline, optymalizacje aplikacji są dostępne znacznie szybciej niż w przypadku samych profili Cloud.

Jeśli nie używasz profilu Baseline, cały kod aplikacji jest kompilowany w pamięci po zinterpretowaniu lub do pliku odex w tle, gdy urządzenie jest bezczynne. W takiej sytuacji po zainstalowaniu lub zaktualizowaniu aplikacji po raz pierwszy, zanim nowe ścieżki zostaną zoptymalizowane, może to negatywnie wpłynąć na wygodę użytkowników podczas korzystania z aplikacji.

4. Konfigurowanie modułu generatora profilu Baseline

Profile podstawowe możesz generować z klasą testową instrumentacji, która wymaga dodania do projektu nowego modułu Gradle. Najłatwiejszym sposobem dodania go do projektu jest użycie kreatora modułów Android Studio dostarczanego z Android Studio Hedgehog lub nowszym.

Otwórz nowe okno kreatora modułu, klikając prawym przyciskiem myszy projekt lub moduł w panelu Projekt i wybierz Nowy > Moduł.

232b04efef485e9c.png

W otwartym oknie w panelu Templates (Szablony) wybierz Baseline Profile Generator.

b191fe07969e8c26.png

Oprócz standardowych parametrów, takich jak nazwa modułu, nazwa pakietu, język czy język konfiguracji kompilacji, są dostępne 2 rodzaje danych wejściowych, które nie są typowe dla nowego modułu: aplikacja docelowa i używanie urządzenia zarządzanego za pomocą Gradle.

Aplikacja docelowa to moduł aplikacji używany do generowania profili podstawowych. Jeśli w projekcie masz więcej niż jeden moduł aplikacji, wybierz ten, dla którego chcesz uruchomić generatory.

Pole wyboru Użyj urządzenia zarządzanego przez Gradle powoduje, że moduł uruchamia generatory profili bazowych w automatycznie zarządzanych emulatorach Androida. Więcej informacji o urządzeniach zarządzanych za pomocą Gradle znajdziesz w artykule Skalowanie testów na urządzeniach zarządzanych za pomocą Gradle. Jeśli odznaczysz tę opcję, generatory będą używać dowolnego połączonego urządzenia.

Po zdefiniowaniu wszystkich szczegółów nowego modułu kliknij Zakończ, aby kontynuować jego tworzenie.

Zmiany wprowadzone przez kreatora modułów

Kreator modułów wprowadza kilka zmian w projekcie.

Dodaje moduł Gradle o nazwie baselineprofile lub nazwę wybraną w kreatorze.

Ten moduł korzysta z wtyczki com.android.test, która informuje Gradle, aby nie umieszczać go w Twojej aplikacji, więc może zawierać tylko kod testowy lub testy porównawcze. Stosuje również wtyczkę androidx.baselineprofile, która umożliwia automatyzację generowania profili podstawowych.

Kreator wprowadza też zmiany w wybranym docelowym module aplikacji. W szczególności stosuje wtyczkę androidx.baselineprofile, dodaje zależność androidx.profileinstaller i dodaje zależność baselineProfile do nowo utworzonego modułu build.gradle(.kts):

plugins {
  id("androidx.baselineprofile")
}

dependencies {
  // ...
  implementation("androidx.profileinstaller:profileinstaller:1.3.0")
  "baselineProfile"(project(mapOf("path" to ":baselineprofile")))
}

Dodanie zależności androidx.profileinstaller umożliwia Ci:

  • Weryfikuj lokalnie wzrost wydajności wygenerowanych profili podstawowych.
  • korzystać z profili podstawowych na Androidzie 7 (poziom interfejsu API 24) i Androidzie 8 (poziom interfejsu API 26), które nie obsługują profili Cloud;
  • Używaj profili podstawowych na urządzeniach bez Usług Google Play.

Zależność baselineProfile(project(":baselineprofile")) informuje Gradle, z którego modułu musi pobrać wygenerowane profile podstawowe.

Po utworzeniu projektu utwórz klasę generatora profili podstawowych.

5. Utwórz generator profilu podstawowego

Zwykle generujesz profile podstawowe na potrzeby typowych ścieżek użytkowników w Twojej aplikacji.

Kreator modułów tworzy podstawową klasę testową BaselineProfileGenerator, która potrafi wygenerować profil podstawowy dla uruchamiania aplikacji, i wygląda tak:

@RunWith(AndroidJUnit4::class)
@LargeTest
class BaselineProfileGenerator {

    @get:Rule
    val rule = BaselineProfileRule()

    @Test
    fun generate() {
        rule.collect("com.example.baselineprofiles_codelab") {
            // This block defines the app's critical user journey. This is where you
            // optimize for app startup. You can also navigate and scroll
            // through your most important UI.

            // Start default activity for your app.
            pressHome()
            startActivityAndWait()

            // TODO Write more interactions to optimize advanced journeys of your app.
            // For example:
            // 1. Wait until the content is asynchronously loaded.
            // 2. Scroll the feed content.
            // 3. Navigate to detail screen.

            // Check UiAutomator documentation for more information about how to interact with the app.
            // https://d.android.com/training/testing/other-components/ui-automator
        }
    }
}

Ta klasa używa reguły testowej BaselineProfileRule i zawiera 1 metodę testową do wygenerowania profilu. Punktem początkowym do wygenerowania profilu jest funkcja collect(). Wymaga tylko 2 parametrów:

  • packageName: pakiet aplikacji.
  • profileBlock: ostatni parametr lambda.

Funkcja lambda profileBlock określa interakcje, które obejmują typowe ścieżki użytkownika Twojej aplikacji. Biblioteka uruchamia profileBlock kilka razy, gromadzi wywołane klasy i funkcje, a następnie generuje na urządzeniu profil bazowy z kodem, który ma zostać zoptymalizowany.

Domyślnie utworzona klasa generatora zawiera interakcje rozpoczynające domyślny Activity i czeka, aż pierwsza klatka aplikacji zostanie wyrenderowana za pomocą metody startActivityAndWait().

Rozszerz generator o niestandardowe ścieżki

Możesz też zauważyć, że wygenerowana klasa zawiera TODO, aby napisać więcej interakcji, aby zoptymalizować zaawansowane ścieżki aplikacji. Dzięki temu możesz optymalizować wydajność po uruchomieniu aplikacji.

W naszej przykładowej aplikacji możesz zidentyfikować te ścieżki, wykonując te czynności:

  1. Uruchom aplikację. To jest już częściowo uwzględnione w wygenerowanej klasie.
  2. Poczekaj, aż treści zostaną wczytane asynchronicznie.
  3. Przewiń listę przekąsek.
  4. Otwórz szczegóły przekąski.

Zmodyfikuj generator tak, aby zawierał funkcje, które obejmują typowe ścieżki, w tym fragmencie:

// ...
rule.collect("com.example.baselineprofiles_codelab") {
    // This block defines the app's critical user journey. This is where you
    // optimize for app startup. You can also navigate and scroll
    // through your most important UI.

    // Start default activity for your app.
    pressHome()
    startActivityAndWait()

    // TODO Write more interactions to optimize advanced journeys of your app.
    // For example:
    // 1. Wait until the content is asynchronously loaded.
    waitForAsyncContent()
    // 2. Scroll the feed content.
    scrollSnackListJourney()
    // 3. Navigate to detail screen.
    goToSnackDetailJourney()

    // Check UiAutomator documentation for more information about how to interact with the app.
    // https://d.android.com/training/testing/other-components/ui-automator
}
// ...

Teraz napisz interakcje dla każdej wspomnianej ścieżki. Możesz zapisać ją jako funkcję rozszerzenia MacrobenchmarkScope, aby mieć dostęp do oferowanych przez nią parametrów i funkcji. Taki sposób zapisu pozwala ponownie wykorzystać interakcje z testami porównawczymi do weryfikowania wzrostu skuteczności.

Oczekiwanie na zawartość asynchroniczną

Wiele aplikacji ma tzw. stan w pełni wyświetlany, który podczas uruchamiania aplikacji stosuje asynchroniczne ładowanie. Oznacza to, że system informuje system, kiedy zawartość jest wczytywana i renderowana, a użytkownik może z niej korzystać. Poczekaj na stan w generatorze (waitForAsyncContent) z tymi interakcjami:

  1. Znajdź listę przekąsek.
  2. Poczekaj, aż na ekranie pojawią się niektóre elementy z listy.
fun MacrobenchmarkScope.waitForAsyncContent() {
   device.wait(Until.hasObject(By.res("snack_list")), 5_000)
   val contentList = device.findObject(By.res("snack_list"))
   // Wait until a snack collection item within the list is rendered.
   contentList.wait(Until.hasObject(By.res("snack_collection")), 5_000)
}

Ścieżka przewijanych list

W przypadku przewijanej listy z przekąskami (scrollSnackListJourney) możesz obserwować te interakcje:

  1. Znajdź element interfejsu z listą przekąsek.
  2. Ustaw marginesy gestów, aby nie uruchamiały nawigacji systemowej.
  3. Przewiń listę i poczekaj, aż interfejs się ustabilizuje.
fun MacrobenchmarkScope.scrollSnackListJourney() {
   val snackList = device.findObject(By.res("snack_list"))
   // Set gesture margin to avoid triggering gesture navigation.
   snackList.setGestureMargin(device.displayWidth / 5)
   snackList.fling(Direction.DOWN)
   device.waitForIdle()
}

Otwórz szczegóły ścieżki

Ostatnia ścieżka (goToSnackDetailJourney) implementuje te interakcje:

  1. Znajdź listę przekąsek i wszystkich przekąsek, z którymi możesz pracować.
  2. Wybierz element na liście.
  3. Kliknij element i poczekaj na wczytanie ekranu szczegółów. Możesz wykorzystać fakt, że lista przekąsek nie będzie już widoczna na ekranie.
fun MacrobenchmarkScope.goToSnackDetailJourney() {
    val snackList = device.findObject(By.res("snack_list"))
    val snacks = snackList.findObjects(By.res("snack_item"))
    // Select snack from the list based on running iteration.
    val index = (iteration ?: 0) % snacks.size
    snacks[index].click()
    // Wait until the screen is gone = the detail is shown.
    device.wait(Until.gone(By.res("snack_list")), 5_000)
}

Po zdefiniowaniu wszystkich interakcji potrzebnych do uruchomienia generatora profilu bazowego należy określić urządzenie, na którym ma on działać.

6. Przygotuj urządzenie do uruchomienia generatora

Aby wygenerować profile podstawowe, zalecamy użycie emulatora takiego jak urządzenie zarządzane przez Gradle albo urządzenia z Androidem 13 (API 33) lub nowszym.

Aby można było odtwarzać proces i zautomatyzować generowanie profili podstawowych, możesz użyć urządzeń zarządzanych za pomocą Gradle. Urządzenia zarządzane przez Gradle umożliwiają przeprowadzanie testów w emulatorze Androida bez konieczności ręcznego uruchamiania i montowania go. Więcej informacji o urządzeniach zarządzanych za pomocą Gradle znajdziesz w artykule Skalowanie testów na urządzeniach zarządzanych za pomocą Gradle.

Aby zdefiniować urządzenie zarządzane przez Gradle, dodaj jego definicję do pliku build.gradle.kts modułu :baselineprofile, tak jak w tym fragmencie:

android {
  // ...

  testOptions.managedDevices.devices {
    create<ManagedVirtualDevice>("pixel6Api31") {
        device = "Pixel 6"
        apiLevel = 31
        systemImageSource = "aosp"
    }
  } 
}

W tym przypadku korzystamy z Androida 11 (poziom interfejsu API 31) i obraz systemu aosp ma dostęp do roota.

Następnie skonfiguruj wtyczkę Gradle profilu Baseline tak, aby używała zdefiniowanego urządzenia zarządzanego przez Gradle. Aby to zrobić, dodaj nazwę urządzenia we właściwości managedDevices i wyłącz funkcję useConnectedDevices, jak pokazano w tym fragmencie:

android {
  // ...
}

baselineProfile {
   managedDevices += "pixel6Api31"
   useConnectedDevices = false
}

dependencies {
  // ...
}

Następnie wygeneruj profil podstawowy.

7. Wygeneruj profil podstawowy

Gdy urządzenie będzie gotowe, możesz utworzyć profil podstawowy. Wtyczka do obsługi profilu Baseline do Gradle tworzy zadania Gradle, aby zautomatyzować cały proces uruchamiania klasy testowej generatora i stosowania wygenerowanych profili podstawowych w aplikacji.

Nowy kreator modułów utworzył konfigurację uruchamiania, aby móc szybko uruchomić zadanie Gradle ze wszystkimi niezbędnymi parametrami do uruchomienia bez konieczności przełączania się między terminalem a Androidem Studio

Aby go uruchomić, znajdź konfigurację uruchamiania Generate Baseline Profile i kliknij przycisk Uruchom 599be5a3531f863b.png.

6911ecf1307a213f.png

Zadanie uruchomi zdefiniowany wcześniej obraz emulatora. Uruchom kilka razy interakcje z klasy testowej BaselineProfileGenerator, a potem odłącz emulator i przekaż dane wyjściowe do Android Studio.

Gdy generator zakończy działanie, wtyczka Gradle automatycznie umieszcza wygenerowany plik baseline-prof.txt w aplikacji docelowej (moduł :app) w folderze src/release/generated/baselineProfile/.

fa0f52de5d2ce5e8.png

(Opcjonalnie) Uruchom generator z wiersza poleceń

Możesz też uruchomić generator z poziomu wiersza poleceń. Możesz wykorzystać zadanie utworzone przez urządzenie zarządzane przez Gradle – :app:generateBaselineProfile. To polecenie uruchamia wszystkie testy w projekcie zdefiniowane przez zależność baselineProfile(project(:baselineProfile)). Moduł zawiera też analizę porównawczą, dzięki której można później sprawdzić wzrost wydajności. Testy te kończą się niepowodzeniem i ostrzegamy przed przeprowadzeniem testów porównawczych w emulatorze.

android
   .testInstrumentationRunnerArguments
   .androidx.benchmark.enabledRules=BaselineProfile

W tym celu możesz przefiltrować wszystkie generatory profili podstawowych przy użyciu poniższego argumentu uruchamiającego instrumentacja, a wszystkie testy porównawcze zostaną pominięte:

Całe polecenie wygląda tak:

./gradlew :app:generateBaselineProfile -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile

Rozpowszechnianie aplikacji za pomocą profili podstawowych

Po wygenerowaniu profilu podstawowego i skopiowaniu go do kodu źródłowego aplikacji utwórz jej wersję produkcyjną w zwykły sposób. Aby rozpowszechnić profile podstawowe wśród użytkowników, nie musisz nic robić. Są one wybierane przez wtyczkę Androida do obsługi Gradle podczas kompilacji i dołączane do pakietu aplikacji na Androida lub pliku APK. Następnie prześlij kompilację do Google Play.

Gdy użytkownicy zainstalują aplikację lub zaktualizują ją z poprzedniej wersji, zostanie również zainstalowany profil Baseline, dzięki czemu aplikacja będzie działać lepiej po pierwszym uruchomieniu.

W następnym kroku pokazujemy, jak za pomocą profili podstawowych sprawdzić, na ile poprawia się wydajność aplikacji.

8. (Opcjonalnie) Dostosuj generowanie profili podstawowych

Wtyczka Gradle profili Baseline zapewnia opcje umożliwiające dostosowanie sposobu generowania profili do własnych potrzeb. Możesz zmienić to działanie za pomocą bloku konfiguracji baselineProfile { } w skryptach kompilacji.

Blok konfiguracji w module :baselineprofile wpływa na sposób uruchamiania generatorów z możliwością dodania elementu managedDevices i decydowania, czy należy używać urządzeń useConnectedDevices czy zarządzanych przez Gradle.

Blok konfiguracji w module celu :app określa miejsce i sposób generowania profili oraz miejsce ich zapisywania. Możesz zmienić te parametry:

  • automaticGenerationDuringBuild: jeśli ta opcja jest włączona, możesz wygenerować profil podstawowy podczas tworzenia kompilacji wersji produkcyjnej. Jest to przydatne, gdy tworzysz aplikację w CI przed wysłaniem aplikacji.
  • saveInSrc: określa, czy wygenerowane profile podstawowe są przechowywane w folderze src/. Możesz też otworzyć ten plik z folderu kompilacji :baselineprofile.
  • baselineProfileOutputDir: określa, gdzie mają być przechowywane wygenerowane profile podstawowe.
  • mergeIntoMain: domyślnie profile podstawowe są generowane dla każdej wersji kompilacji (rodzaj usługi i typ kompilacji). Jeśli chcesz scalić wszystkie profile z elementem src/main, włącz tę flagę.
  • filter: możesz filtrować klasy lub metody, aby uwzględnić wygenerowane profile podstawowe albo z nich wykluczyć. Może to być przydatne dla programistów bibliotek, którzy chcą uwzględnić tylko kod z biblioteki.

9. Sprawdzanie poprawy wydajności uruchamiania

Po wygenerowaniu profilu Baseline i dodaniu go do swojej aplikacji sprawdź, czy ma on pożądany wpływ na jej wydajność.

Nowy kreator modułu tworzy klasę porównawczą o nazwie StartupBenchmarks. Zawiera ona test porównawczy do pomiaru czasu uruchamiania aplikacji i porównuje ją z okresem, w którym aplikacja używa profili podstawowych.

Klasa wygląda tak:

@RunWith(AndroidJUnit4::class)
@LargeTest
class StartupBenchmarks {

    @get:Rule
    val rule = MacrobenchmarkRule()

    @Test
    fun startupCompilationNone() =
        benchmark(CompilationMode.None())

    @Test
    fun startupCompilationBaselineProfiles() =
        benchmark(CompilationMode.Partial(BaselineProfileMode.Require))

    private fun benchmark(compilationMode: CompilationMode) {
        rule.measureRepeated(
            packageName = "com.example.baselineprofiles_codelab",
            metrics = listOf(StartupTimingMetric()),
            compilationMode = compilationMode,
            startupMode = StartupMode.COLD,
            iterations = 10,
            setupBlock = {
                pressHome()
            },
            measureBlock = {
                startActivityAndWait()

                // TODO Add interactions to wait for when your app is fully drawn.
                // The app is fully drawn when Activity.reportFullyDrawn is called.
                // For Jetpack Compose, you can use ReportDrawn, ReportDrawnWhen and ReportDrawnAfter
                // from the AndroidX Activity library.

                // Check the UiAutomator documentation for more information on how to
                // interact with the app.
                // https://d.android.com/training/testing/other-components/ui-automator
            }
        )
    }
}

Wykorzystuje ono usługę MacrobenchmarkRule, która może przeprowadzać testy porównawcze aplikacji i zbierać dane o jej działaniu. Punktem wejścia testu porównawczego jest funkcja measureRepeated z reguły.

Wymaga kilku parametrów:

  • packageName:, którą aplikację chcesz mierzyć.
  • metrics: typ informacji, który chcesz zmierzyć w ramach testu porównawczego.
  • iterations: ile razy wartość referencyjna się powtarza.
  • startupMode: jak ma się uruchamiać aplikacja na początku testu porównawczego.
  • setupBlock: jakie interakcje z aplikacją muszą wystąpić przed pomiarem.
  • measureBlock: interakcje z aplikacją, które chcesz objąć testem porównawczym.

Klasa testowa zawiera też 2 testy: startupCompilationeNone() i startupCompilationBaselineProfiles(), które wywołują funkcję benchmark() z różną wartością compilationMode.

CompilationMode

Parametr CompilationMode określa, w jaki sposób aplikacja jest wstępnie skompilowana w kod maszynowy. Dostępne opcje:

  • DEFAULT: częściowo wstępnie kompiluje aplikację przy użyciu profili podstawowych (jeśli są dostępne). Jest ona używana, jeśli nie zastosowano żadnego parametru compilationMode.
  • None(): resetuje stan kompilacji aplikacji i nie kompiluje jej wstępnie. Kompilacja Just-in-time (JIT) jest nadal włączona podczas uruchamiania aplikacji.
  • Partial(): wstępnie kompiluje aplikację za pomocą profili podstawowych, uruchomień rozgrzewkowych lub obu tych metod.
  • Full(): wstępnie kompiluje cały kod aplikacji. To jedyna opcja na Androidzie 6 (API 23) i starszych.

Jeśli chcesz zacząć optymalizować wydajność aplikacji, możesz wybrać tryb kompilacji DEFAULT, ponieważ jest on podobny do tego, kiedy aplikacja instaluje się z Google Play. Jeśli chcesz porównać korzyści związane z wydajnością zapewniane przez profile podstawowe, możesz to zrobić, porównując wyniki trybu kompilacji None i Partial.

Modyfikowanie testu porównawczego w celu oczekiwania na treści

Testy porównawcze są zapisywane podobnie jak generatory profili podstawowych poprzez wpisanie interakcji z aplikacją. Domyślnie utworzone testy porównawcze czekają tylko na wyrenderowanie pierwszej klatki – podobnie jak w przypadku interfejsu BaselineProfileGenerator. Zalecamy więc poprawienie wyników w oczekiwaniu na treści asynchroniczne.

Możesz to zrobić, używając funkcji rozszerzeń napisanych dla generatora. Ponieważ test porównawczy rejestruje czasy uruchamiania (za pomocą StartupTimingMetric()), zalecamy, aby poczekać na wyświetlenie treści asynchronicznej w tym miejscu i zapisać osobną analizę porównawczą dla innych ścieżek użytkownika zdefiniowanych w generatorze.

// ...
measureBlock = {
   startActivityAndWait()

   // The app is fully drawn when Activity.reportFullyDrawn is called.
   // For Jetpack Compose, you can use ReportDrawn, ReportDrawnWhen and ReportDrawnAfter
   // from the AndroidX Activity library.
   waitForAsyncContent() // <------- Added to wait for async content.

   // Check the UiAutomator documentation for more information on how to
   // interact with the app.
   // https://d.android.com/training/testing/other-components/ui-automator
}

Przeprowadzanie testów porównawczych

Testy porównawcze możesz przeprowadzać tak samo jak testy zinstruowane. Możesz uruchomić funkcję testową lub uruchomić całą klasę z ikoną rynny.

587b04d1a76d1e9d.png

Sprawdź, czy masz wybrane urządzenie fizyczne, ponieważ uruchamianie testów porównawczych w emulatorze Androida kończy się niepowodzeniem w czasie działania i wyświetla się ostrzeżenie, że test porównawczy może zwrócić nieprawidłowe wyniki. Technicznie rzecz biorąc, możesz użyć emulatora, ale mierzysz wydajność hosta. Jeśli obciążenie jest bardzo duże, wyniki testu porównawczego będą działać wolniej i odwrotnie.

94e0da86b6f399d5.png

Po przeprowadzeniu testów porównawczych aplikacja jest ponownie skompilowana i stosowana do testów porównawczych. Testy porównawcze wymagają kilkukrotnego uruchomienia, zatrzymania, a nawet ponownego zainstalowania aplikacji na podstawie zdefiniowanej przez Ciebie iterations.

Po zakończeniu testów porównawczych w danych wyjściowych Android Studio pojawią się kody czasowe, tak jak na tym zrzucie ekranu:

282f90d5f6ff5196.png

Jak widać na zrzucie ekranu, czas uruchomienia aplikacji jest różny w przypadku poszczególnych funkcji CompilationMode. Wartości mediany są pokazane w tej tabeli:

timeToInitialDisplay [ms]

czasToFullDisplay [ms]

Brak

202,2

818,8

BaselineProfiles

193,7

637,9

Poprawa

4%

28%

Różnica między trybami kompilacji timeToFullDisplay wynosi 180 ms,co oznacza wzrost o ok. 28% dzięki samemu profilowi odniesienia. CompilationNone działa gorzej, ponieważ podczas uruchamiania aplikacji urządzenie musi wykonać największą kompilację JIT. Model CompilationBaselineProfiles działa lepiej, ponieważ częściowa kompilacja z użyciem AOT – kod, który ma największe szanse na zastosowanie przez użytkownika, kompiluje kod niekrytyczny, który nie jest wstępnie skompilowany, więc nie musi się od razu ładować.

10. (Opcjonalnie) Sprawdź poprawę wydajności przewijania

Podobnie jak w poprzednim kroku możesz mierzyć i weryfikować skuteczność przewijania. Najpierw utwórz klasę testową ScrollBenchmarks z regułą testu porównawczego i 2 metodami testowania, które korzystają z różnych trybów kompilacji:

@LargeTest
@RunWith(AndroidJUnit4::class)
class ScrollBenchmarks {

   @get:Rule
   val rule = MacrobenchmarkRule()

   @Test
   fun scrollCompilationNone() = scroll(CompilationMode.None())

   @Test
   fun scrollCompilationBaselineProfiles() = scroll(CompilationMode.Partial())

   private fun scroll(compilationMode: CompilationMode) {
       // TODO implement
   }
}

Z poziomu metody scroll użyj funkcji measureRepeated z wymaganymi parametrami. W przypadku parametru metrics użyj wartości FrameTimingMetric, która mierzy czas potrzebny do utworzenia ramek interfejsu:

private fun scroll(compilationMode: CompilationMode) {
   rule.measureRepeated(
       packageName = "com.example.baselineprofiles_codelab",
       metrics = listOf(FrameTimingMetric()),
       compilationMode = compilationMode,
       startupMode = StartupMode.WARM,
       iterations = 10,
       setupBlock = {
           // TODO implement
       },
       measureBlock = {
           // TODO implement
       }
   )
}

Tym razem musisz bardziej podzielić interakcje między setupBlock i measureBlock, aby zmierzyć czasy wyświetlania klatek tylko w przypadku pierwszego układu i przewijania treści. Dlatego funkcje, które uruchamiają domyślny ekran, umieść w elemencie setupBlock, a już utworzone funkcje rozszerzeń waitForAsyncContent() i scrollSnackListJourney() w elemencie measureBlock:

private fun scroll(compilationMode: CompilationMode) {
   rule.measureRepeated(
       packageName = "com.example.baselineprofiles_codelab",
       metrics = listOf(FrameTimingMetric()),
       compilationMode = compilationMode,
       startupMode = StartupMode.WARM,
       iterations = 10,
       setupBlock = {
           pressHome()
           startActivityAndWait()
       },
       measureBlock = {
           waitForAsyncContent()
           scrollSnackListJourney()
       }
   )
}

Gdy test porównawczy będzie gotowy, możesz go uruchomić tak jak poprzednio, aby uzyskać wyniki, jak pokazano na tym zrzucie ekranu:

84aa99247226fc3a.png

FrameTimingMetric oblicza czas trwania klatek w milisekundach (frameDurationCpuMs) w 50, 90, 95 i 99 centylu. W Androidzie 12 (poziom interfejsu API 31) i nowszych zwraca też czas, o którym klatki przekroczyły limit (frameOverrunMs). Wartość może być ujemna, co oznacza, że na wyprodukowanie klatki pozostał dodatkowy czas.

Na podstawie wyników widać, że czas renderowania klatki w przypadku urządzenia CompilationBaselineProfiles jest średnio krótszy o 2 ms, co może nie być zauważalne dla użytkowników. W przypadku innych centyli wyniki są jednak bardziej oczywiste. W przypadku P99 różnica wynosi 43, 5 ms, czyli więcej niż 3 pominięte klatki na urządzeniu działającym z szybkością 90 kl./s. Na przykład na Pixelu 6 jest to 1000 ms / 90 FPS = maksymalny czas renderowania klatki, który wynosi około 11 ms.

11. Gratulacje

Gratulujemy ukończenia tego ćwiczenia z programowania i poprawienia wydajności aplikacji przy użyciu profili podstawowych.

Dodatkowe zasoby

Zapoznaj się z tymi dodatkowymi materiałami:

Dokumentacja