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

1. Zanim zaczniesz

Z tego modułu praktycznego dowiesz się, jak generować profile referencyjne, aby optymalizować wydajność aplikacji, oraz jak weryfikować korzyści z użycia tych profili.

Czego potrzebujesz

Jakie zadania wykonasz

  • Skonfiguruj projekt, aby używać generatorów profili podstawowych.
  • generować profile bazowe, aby optymalizować uruchamianie aplikacji i wydajność przewijania;
  • Sprawdź wzrost wydajności za pomocą biblioteki Jetpack Macrobenchmark.

Czego się nauczysz

  • Profil podstawowy i sposób, w jaki może on poprawić skuteczność aplikacji.
  • Jak generować profile podstawowe
  • Wzrost wydajności profili podstawowych.

2. Przygotowanie

Aby rozpocząć, skopiuj repozytorium GitHuba z wiersza poleceń, używając tego polecenia:

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

Możesz też pobrać 2 pliki ZIP:

Otwieranie projektu w Android Studio

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

Przykładowa aplikacja

W tym ćwiczeniu będziesz pracować z aplikacją JetSnack. To wirtualna aplikacja do zamawiania przekąsek, która korzysta z Jetpack Compose.

Aby mierzyć skuteczność aplikacji, musisz znać strukturę interfejsu użytkownika i sposób działania aplikacji, aby móc uzyskiwać dostęp do elementów interfejsu użytkownika z benchmarków. 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 zwiększają szybkość wykonywania kodu o około 30% w porównaniu z pierwszym uruchomieniem, ponieważ unikają interpretacji i etapów kompilacji w czasie wykonywania (JIT) w przypadku uwzględnionych ścieżek kodu. Dzięki udostępnieniu profilu bazowego w aplikacji lub bibliotece środowisko wykonawcze Android Runtime (ART) może optymalizować uwzględnione ścieżki kodu za pomocą kompilacji z wyprzedzeniem (AOT), co zapewnia poprawę wydajności dla każdego nowego użytkownika i przy każdej aktualizacji aplikacji. Optymalizacja na podstawie profilu (PGO) pozwala aplikacjom optymalizować uruchamianie, zmniejszać opóźnienia interakcji i poprawiać ogólną wydajność w czasie działania dla użytkowników od pierwszego uruchomienia.

Dzięki profilowi bazowemu wszystkie interakcje użytkownika, takie jak uruchamianie aplikacji, poruszanie się między ekranami czy przewijanie treści, są płynniejsze od pierwszego uruchomienia. Zwiększenie szybkości i czułości aplikacji prowadzi do zwiększenia liczby aktywnych użytkowników dziennie oraz średniej częstotliwości ponownych 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. Prowadzona kompilacja AOT nie zależy od urządzeń użytkowników i może być wykonywana raz na wersję na komputerze deweloperskim, a nie na urządzeniu mobilnym. Dzięki udostępnianiu wersji z profilem bazowym optymalizacje aplikacji są dostępne znacznie szybciej niż w przypadku korzystania tylko z profili w chmurze.

Jeśli nie używasz profilu podstawowego, cały kod aplikacji jest kompilowany w pamięci w trybie JIT po interpretacji lub w tle w pliku odex, gdy urządzenie jest nieaktywne. Użytkownicy mogą mieć problemy z uruchomieniem aplikacji po jej zainstalowaniu lub zaktualizowaniu po raz pierwszy, zanim nowe ścieżki zostaną zoptymalizowane.

4. Konfigurowanie modułu generatora profilu Baseline

Profile bazowe możesz generować za pomocą klasy testu instrumentacji, która wymaga dodania do projektu nowego modułu Gradle. Najłatwiej dodać je do projektu za pomocą kreatora modułów Android Studio, który jest dostępny w wersji Hedgehog lub nowszej.

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 oknie, które się otworzy, w panelu Szablony kliknij Generator profilu podstawowego.

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.

Docelowa aplikacja to moduł aplikacji, dla którego generowane są profile podstawowe. Jeśli w projekcie masz więcej niż 1 moduł aplikacji, wybierz ten, dla którego chcesz uruchomić generatory.

Zaznaczenie pola wyboru Użyj urządzenia zarządzanego przez Gradle powoduje, że moduł uruchamia generatory profili referencyjnych na 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łu wprowadza w projekcie kilka zmian.

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

Ten moduł korzysta z wtyczki com.android.test, która informuje Gradle, aby nie uwzględniać go w aplikacji. Może on zawierać tylko kod testowy lub testy porównawcze. Dotyczy to też wtyczki androidx.baselineprofile, która umożliwia automatyzację generowania profili podstawowych.

Wprowadza on też zmiany w wybranym module aplikacji docelowej. 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:

  • Lokalnie sprawdź wzrost wydajności wygenerowanych profili referencyjnych.
  • Używaj profili podstawowych na Androidzie 7 (poziom interfejsu API 24) i Androidzie 8 (poziom interfejsu API 26), które nie obsługują profili w chmurze.
  • 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. Tworzenie generatora profilu podstawowego

Zazwyczaj generujesz Profile podstawowe dla typowych ścieżek użytkowników w aplikacji.

Kreator modułu tworzy podstawową klasę testów BaselineProfileGenerator, która może generować profil podstawowy na potrzeby uruchamiania aplikacji. Wygląda on 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 jedną metodę testową do generowania profilu. Punkt wejścia do generowania profilu to funkcja collect(). Wymaga tylko 2 parametrów:

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

W lambda profileBlock określasz interakcje, które obejmują typowe ścieżki użytkownika w aplikacji. Biblioteka uruchamia profileBlock kilka razy, zbiera wywołane klasy i funkcje oraz generuje na urządzeniu profil bazowy z kodem do optymalizacji.

Utworzona klasa generatora zawiera domyślne interakcje, które uruchamiają domyślną funkcję Activity i czekają, aż zostanie wyrenderowany pierwszy kadr aplikacji za pomocą metody startActivityAndWait().

Rozszerzanie generatora o niestandardowe ścieżki

Wygenerowana klasa zawiera też funkcję TODO, która umożliwia zapisywanie dodatkowych interakcji w celu optymalizacji zaawansowanych ścieżek w aplikacji. Jest to zalecane, aby można było optymalizować wydajność poza uruchamianiem aplikacji.

W naszej przykładowej aplikacji możesz je rozpoznać, wykonując te czynności:

  1. Uruchom aplikację. Ta kwestia jest już częściowo objęta wygenerowaną klasą.
  2. Poczekaj, aż treści zostaną załadowane asynchronicznie.
  3. Przewiń listę przekąsek.
  4. Otwórz szczegóły przekąski.

Zmień generator, aby zawierał opisane funkcje, które obejmują typowe ścieżki w tym fragmencie kodu:

// ...
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 opisz interakcje na każdej z wymienionych ścieżek. Możesz zapisać ją jako funkcję rozszerzenia MacrobenchmarkScope, aby mieć dostęp do oferowanych przez nią parametrów i funkcji. Dzięki temu możesz ponownie używać interakcji z testami porównawczymi, aby weryfikować wzrost skuteczności.

Poczekaj na treści asynchroniczne

Wiele aplikacji korzysta z niektórych form wczytywania asynchronicznego podczas uruchamiania, czyli stanu pełnego wyświetlania, który informuje system, kiedy treść została wczytana i wygenerowana, i umożliwia użytkownikowi interakcję z treścią. Poczekaj na stan w generatorze (waitForAsyncContent) z tymi interakcjami:

  1. Znajdź listę przekąsek na karmę.
  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 użytkownika listy skrótów.
  2. Ustaw marginesy gestów, aby nie aktywować nawigacji systemowej.
  3. Przewiń listę i zaczekaj, 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()
}

Przejdź do szczegółowej ścieżki

Ostatnia podróż (goToSnackDetailJourney) zawiera te interakcje:

  1. Znajdź listę przekąsek i wszystkie przekąski, z którymi możesz pracować.
  2. Wybierz element z listy.
  3. Kliknij element i zaczekaj, aż wczyta się ekran z informacjami. 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 podstawowego musisz 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ą uruchamianie testów na emulatorze Androida bez konieczności uruchamiania i zatrzymywania go ręcznie. Więcej informacji o urządzeniach zarządzanych przez Gradle znajdziesz w artykule Testowanie na dużą skalę za pomocą urządzeń zarządzanych przez 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 używamy Androida 11 (poziom interfejsu API 31), a obraz systemu aosp umożliwia dostęp z rootem.

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 do właściwości managedDevices i wyłącz useConnectedDevices, jak pokazano w tym fragmencie kodu:

android {
  // ...
}

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

dependencies {
  // ...
}

Następnie wygeneruj profil bazowy.

7. Generowanie profilu podstawowego

Gdy urządzenie będzie gotowe, możesz utworzyć profil referencyjny. Wtyczka Gradle do profilu podstawowego tworzy zadania Gradle, aby zautomatyzować cały proces uruchamiania klasy testu generatora i zastosowywania wygenerowanych profili podstawowych w aplikacji.

Kreator nowego modułu utworzył konfigurację uruchomienia, aby można było szybko uruchomić zadanie Gradle ze wszystkimi niezbędnymi parametrami bez konieczności przełączania się między terminalem a Android 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. Kilka razy uruchom interakcje z klasy testowej BaselineProfileGenerator, a potem zamknij emulator i prześlij dane wyjściowe do Android Studio.

Gdy generator zakończy pracę, wtyczka Gradle automatycznie przeniesie wygenerowany baseline-prof.txt do docelowej aplikacji (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 użyć zadania utworzonego przez urządzenie zarządzane przez Gradle – :app:generateBaselineProfile. To polecenie uruchamia wszystkie testy w projekcie zdefiniowane przez zależność baselineProfile(project(:baselineProfile)). Ponieważ moduł zawiera też testy porównawcze służące do późniejszej weryfikacji wzrostu wydajności, testy te nie przechodzą z ostrzeżeniem o niemożności uruchamiania testów porównawczych na 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ły wiersz polecenia wygląda tak:

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

Rozpowszechnianie aplikacji za pomocą profili referencyjnych

Po wygenerowaniu profilu podstawowego i skopiowaniu go do kodu źródłowego aplikacji utwórz jej wersję produkcyjną w zwykły sposób. Nie musisz nic robić, aby rozpowszechnić profile podstawowe wśród użytkowników. Są one wybierane przez wtyczkę Android Gradle podczas kompilacji i dodawane do pakietu AAB 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 podstawowy, co zwiększy wydajność już po pierwszym uruchomieniu.

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

8. (Opcjonalnie) Dostosowywanie generowania 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 managedDevices i podjęcia decyzji, czy chcesz useConnectedDevices czy używać urządzeń zarządzanych przez Gradle.

Blok konfiguracji w module docelowym :app określa, gdzie są zapisywane profile i jak są generowane. Możesz zmienić te parametry:

  • automaticGenerationDuringBuild: jeśli ta opcja jest włączona, możesz wygenerować profil bazowy podczas tworzenia wersji produkcyjnej. Jest to przydatne, gdy tworzysz aplikację w CI przed wysłaniem aplikacji.
  • saveInSrc: określa, czy wygenerowane profile bazowe są przechowywane w folderze src/. Możesz też otworzyć ten plik z folderu kompilacji :baselineprofile.
  • baselineProfileOutputDir: określa miejsce przechowywania wygenerowanych profili podstawowych.
  • mergeIntoMain: domyślnie profile podstawowe są generowane dla każdej wersji (wersja produktu i typ kompilacji). Jeśli chcesz scalić wszystkie profile w jeden src/main, możesz to zrobić, włączając tę flagę.
  • filter: możesz filtrować klasy lub metody, które mają być uwzględniane lub wykluczane z wygenerowanych profili podstawowych. Może to być przydatne dla deweloperów bibliotek, którzy chcą uwzględnić tylko kod z biblioteki.

9. Sprawdzanie poprawy wydajności uruchamiania

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

Kreator nowego modułu tworzy klasę referencyjną 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
            }
        )
    }
}

Używa ona MacrobenchmarkRule, która może przeprowadzać testy porównawcze aplikacji i zbierać dane o jej skuteczności. Punktem wejścia testu porównawczego jest funkcja measureRepeated z reguły.

Wymaga kilku parametrów:

  • packageName: aplikację, którą chcesz mierzyć.
  • metrics: typ informacji, które chcesz mierzyć podczas testu porównawczego.
  • iterations: liczba powtórzeń testu porównawczego.
  • startupMode: sposób, w jaki chcesz, aby aplikacja rozpoczynała działanie na początku testu porównawczego.
  • setupBlock: jakie interakcje z aplikacją muszą wystąpić przed pomiarem.
  • measureBlock: interakcje z aplikacją, które chcesz mierzyć podczas testu porównawczego.

Klasa testu zawiera też 2 testy: startupCompilationeNone()startupCompilationBaselineProfiles(), które wywołują funkcję benchmark() z różnymi parametrami compilationMode.

CompilationMode

Parametr CompilationMode określa, jak aplikacja jest wstępnie kompilowana na kod maszynowy. Dostępne są następujące opcje:

  • DEFAULT: częściowo wstępnie kompiluje aplikację za pomocą profili referencyjnych, jeśli są dostępne. Jest ona używana, jeśli nie zastosowano żadnego parametru compilationMode.
  • None(): zeruje stan kompilacji aplikacji i nie kompiluje jej wstępnie. Kompilacja Just-In-Time (JIT) jest nadal włączona podczas wykonywania aplikacji.
  • Partial(): kompiluje aplikację z profilami bazowymi lub z uruchomieniami rozgrzewki lub z obu tymi opcjami.
  • Full(): kompiluje wstępnie cały kod aplikacji. Jest to jedyna opcja w Androidzie 6 (poziom interfejsu API 23) i starszych.

Jeśli chcesz zacząć optymalizować wydajność aplikacji, możesz wybrać tryb kompilacji DEFAULT, ponieważ wydajność jest podobna do tej, gdy aplikacja jest instalowana z Google Play. Jeśli chcesz porównać korzyści w zakresie skuteczności, jakie zapewniają profile podstawowe, możesz to zrobić, porównując wyniki w trybie kompilacji NonePartial.

Modyfikowanie punktu odniesienia, aby oczekiwał na treści

Benchmarki są tworzone podobnie jak generatory profili podstawowych, czyli przez zapisywanie interakcji z aplikacją. Domyślnie utworzone benchmarki czekają tylko na wyrenderowanie pierwszego klatki – podobnie jak BaselineProfileGenerator – dlatego zalecamy ulepszenie tego benchmarka, aby czekał na treści asynchroniczne.

Możesz to zrobić, używając funkcji rozszerzeń napisanych dla generatora. Ten benchmark rejestruje czas uruchamiania (za pomocą funkcji StartupTimingMetric()), dlatego zalecamy uwzględnienie tutaj tylko czasu oczekiwania na treści asynchroniczne, a potem napisanie osobnego benchmarku 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
}

Wykonywanie testów porównawczych

Benchmarki można uruchamiać w taki sam sposób jak testy z użyciem instrumentacji. Możesz uruchomić funkcję testu lub całą lekcję, klikając ikonę kosza obok niej.

587b04d1a76d1e9d.png

Upewnij się, że masz wybrane urządzenie fizyczne, ponieważ uruchomienie testów porównawczych na emulatorze Androida kończy się niepowodzeniem i wyświetleniem ostrzeżenia, że test może dać nieprawidłowe wyniki. Teoretycznie możesz go uruchomić w emulatorze, ale mierzysz wydajność hosta. Jeśli obciążenie jest duże, wyniki testów porównawczych są wolniejsze i odwrotnie.

94e0da86b6f399d5.png

Po uruchomieniu testu porównawczego aplikacja jest ponownie kompilowana, a następnie uruchamia testy porównawcze. Testy porównawcze uruchamiają, zatrzymują i nawet ponownie instalują aplikację kilka razy na podstawie zdefiniowanych przez Ciebie iterations.

Po zakończeniu testów porównawczych możesz zobaczyć czasy w wyjściu Android Studio, jak pokazano na tym zrzucie ekranu:

282f90d5f6ff5196.png

Zrzut ekranu pokazuje, że czas uruchamiania aplikacji jest inny w przypadku każdego CompilationMode. Wartości mediany podane są w tabeli:

timeToInitialDisplay [ms]

timeToFullDisplay [ms]

Brak

202,2

818,8

BaselineProfiles

193,7

637,9

Poprawa

4%

28%

Różnica między trybami kompilacji w przypadku timeToFullDisplay wynosi 180 ms,co oznacza poprawę o ok. 28% dzięki zastosowaniu profilu podstawowego. Aplikacja CompilationNone działa gorzej, ponieważ podczas uruchamiania musi przeprowadzić większość kompilacji JIT. Aplikacja CompilationBaselineProfiles działa lepiej, ponieważ częściowa kompilacja z użyciem profili bazowych AOT kompiluje kod, którego użytkownik najprawdopodobniej użyje, a kod niekrytyczny nie jest wstępnie kompilowany, aby nie musiał być wczytywany od razu.

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

Podobnie jak w poprzednim kroku, możesz zmierzyć i zweryfikować wydajność 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
   }
}

W metodzie 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 setupBlockmeasureBlock, aby mierzyć tylko czas trwania klatek podczas 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ć jak poprzednio, aby uzyskać wyniki widoczne na tym zrzucie ekranu:

84aa99247226fc3a.png

Funkcja FrameTimingMetric zwraca czas trwania klatek w milisekundach (frameDurationCpuMs) na 50, 90, 95 i 99 centylu. W Androidzie 12 (poziom interfejsu API 31) i nowszych zwraca on też informację o tym, o ile czas renderowania klatek przekroczył limit (frameOverrunMs). Wartość może być ujemna, co oznacza, że pozostało dodatkowe ułamek sekundy na wyrenderowanie klatki.

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 wartości procentowych wyniki są bardziej oczywiste. W przypadku P99 różnica wynosi 43, 5 ms, co oznacza ponad 3 pominięte klatki na urządzeniu pracującym z częstotliwością 90 FPS. Na przykład w przypadku Pixela 6 to 1000 ms / 90 FPS = około 11 ms maksymalnego czasu renderowania klatki.

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:

Dokumenty referencyjne