Debugowanie profili podstawowych

W tym dokumencie znajdziesz sprawdzone metody, które pomogą Ci zdiagnozować problemy i zadbać o prawidłowe działanie profili referencyjnych, aby w pełni wykorzystać ich zalety.

Problemy z kompilacją

Jeśli skopiujesz przykładowy profil bazowy z aplikacji Nowości na Androidzie, podczas zadania dotyczącego profilu bazowego mogą wystąpić błędy mówiące, że testów nie można uruchomić na emulatorze:

./gradlew assembleDemoRelease
Starting a Gradle Daemon (subsequent builds will be faster)
Calculating task graph as no configuration cache is available for tasks: assembleDemoRelease
Type-safe project accessors is an incubating feature.

> Task :benchmarks:pixel6Api33DemoNonMinifiedReleaseAndroidTest
Starting 14 tests on pixel6Api33

com.google.samples.apps.nowinandroid.foryou.ScrollForYouFeedBenchmark > scrollFeedCompilationNone[pixel6Api33] FAILED
        java.lang.AssertionError: ERRORS (not suppressed): EMULATOR
        WARNINGS (suppressed):
        ...

Błędy te występują, ponieważ Now na Androidzie używa urządzenia zarządzanego przez Gradle do generowania profilu podstawowego. Błędy są oczekiwane, ponieważ zwykle nie należy uruchamiać testów porównawczych wydajności na emulatorze. Podczas generowania profili podstawowych nie zbierasz danych o wydajności, więc możesz dla wygody uruchomić zbieranie profili podstawowych na emulatorach. Aby używać Profilów podstawowych z emulatorem, wykonaj kompilację i instalację z poziomu wiersza poleceń, a następnie ustaw argument, aby włączyć reguły Profilów podstawowych:

installDemoRelease -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile

Możesz też utworzyć niestandardową konfigurację uruchomienia w Android Studio, aby włączyć profile referencyjne na emulatorach. W tym celu kliknij Uruchom > Edytuj konfiguracje:

Dodawanie niestandardowej konfiguracji testu w aplikacji Now na Androida w celu tworzenia profili referencyjnych
Rysunek 1. Dodaj niestandardową konfigurację wykonania, aby utworzyć bazę profili w aplikacji Google Now na Androidzie.

Problemy przy instalacji

Sprawdź, czy plik APK lub AAB, który sprawdzasz, pochodzi z wersji, która zawiera profile podstawowe:

  1. W Android Studio kliknij Build > Analyze APK (Utwórz > Przeanalizuj APK).
  2. Otwórz plik AAB lub APK.
  3. Jeśli sprawdzasz AAB, profil znajduje się na stronie /BUNDLE-METADATA/com.android.tools.build.profiles/baseline.prof. Jeśli sprawdzasz plik APK, profil znajduje się na stronie /assets/dexopt/baseline.prof.
Sprawdzanie profilu bazowego za pomocą narzędzia APK Viewer w Android Studio
Rysunek 2. Sprawdź, czy istnieje profil bazowy, za pomocą narzędzia APK Viewer w Android Studio.

Profile bazowe muszą być kompilowane na urządzeniu, na którym działa aplikacja. Gdy instalujesz aplikację za pomocą Sklepu Play, Android Studio lub narzędzia wiersza poleceń Gradle Wrapper, kompilacja na urządzeniu odbywa się automatycznie. Gdy aplikacja jest instalowana za pomocą innych narzędzi, biblioteka Jetpacka ProfileInstaller odpowiada za umieszczanie profili w kolejce do kompilacji podczas następnego procesu optymalizacji DEX w tle. W takich przypadkach, jeśli chcesz mieć pewność, że używane są Profile bazowe, konieczne może być wymuszenie ich kompilacji. ProfileVerifier umożliwia sprawdzenie stanu instalacji i kompilacji profilu, jak w tym przykładzie:

Kotlin

private const val TAG = "MainActivity"

class MainActivity : ComponentActivity() {
  ...
  override fun onResume() {
    super.onResume()
    lifecycleScope.launch {
      logCompilationStatus()
    }
  }

  private suspend fun logCompilationStatus() {
     withContext(Dispatchers.IO) {
        val status = ProfileVerifier.getCompilationStatusAsync().await()
        when (status.profileInstallResultCode) {
            RESULT_CODE_NO_PROFILE ->
                Log.d(TAG, "ProfileInstaller: Baseline Profile not found")
            RESULT_CODE_COMPILED_WITH_PROFILE ->
                Log.d(TAG, "ProfileInstaller: Compiled with profile")
            RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION ->
                Log.d(TAG, "ProfileInstaller: Enqueued for compilation")
            RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING ->
                Log.d(TAG, "ProfileInstaller: App was installed through Play store")
            RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST ->
                Log.d(TAG, "ProfileInstaller: PackageName not found")
            RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ ->
                Log.d(TAG, "ProfileInstaller: Cache file exists but cannot be read")
            RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE ->
                Log.d(TAG, "ProfileInstaller: Can't write cache file")
            RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION ->
                Log.d(TAG, "ProfileInstaller: Enqueued for compilation")
            else ->
                Log.d(TAG, "ProfileInstaller: Profile not compiled or enqueued")
        }
    }
}

Java

public class MainActivity extends ComponentActivity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onResume() {
        super.onResume();

        logCompilationStatus();
    }

    private void logCompilationStatus() {
         ListeningExecutorService service = MoreExecutors.listeningDecorator(
                Executors.newSingleThreadExecutor());
        ListenableFuture<ProfileVerifier.CompilationStatus> future =
                ProfileVerifier.getCompilationStatusAsync();
        Futures.addCallback(future, new FutureCallback<>() {
            @Override
            public void onSuccess(CompilationStatus result) {
                int resultCode = result.getProfileInstallResultCode();
                if (resultCode == RESULT_CODE_NO_PROFILE) {
                    Log.d(TAG, "ProfileInstaller: Baseline Profile not found");
                } else if (resultCode == RESULT_CODE_COMPILED_WITH_PROFILE) {
                    Log.d(TAG, "ProfileInstaller: Compiled with profile");
                } else if (resultCode == RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION) {
                    Log.d(TAG, "ProfileInstaller: Enqueued for compilation");
                } else if (resultCode == RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING) {
                    Log.d(TAG, "ProfileInstaller: App was installed through Play store");
                } else if (resultCode == RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST) {
                    Log.d(TAG, "ProfileInstaller: PackageName not found");
                } else if (resultCode == RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ) {
                    Log.d(TAG, "ProfileInstaller: Cache file exists but cannot be read");
                } else if (resultCode
                        == RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE) {
                    Log.d(TAG, "ProfileInstaller: Can't write cache file");
                } else if (resultCode == RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION) {
                    Log.d(TAG, "ProfileInstaller: Enqueued for compilation");
                } else {
                    Log.d(TAG, "ProfileInstaller: Profile not compiled or enqueued");
                }
            }

            @Override
            public void onFailure(Throwable t) {
                Log.d(TAG,
                        "ProfileInstaller: Error getting installation status: " + t.getMessage());
            }
        }, service);
    }
}

Te kody wyników wskazują przyczynę niektórych problemów:

RESULT_CODE_COMPILED_WITH_PROFILE
Profil jest instalowany, kompilowany i używany za każdym razem, gdy uruchamiasz aplikację. To jest wynik, który chcesz zobaczyć.
RESULT_CODE_ERROR_NO_PROFILE_EMBEDDED
W uruchamianym pliku APK nie znaleziono profilu. Jeśli widzisz ten błąd, upewnij się, że używasz wersji, która zawiera profile bazowe, oraz że plik APK zawiera profil.
RESULT_CODE_NO_PROFILE
Podczas instalowania aplikacji w sklepie z aplikacjami lub menedżerze pakietów nie zainstalowano profilu tej aplikacji. Główną przyczyną tego kodu błędu jest to, że instalator profilu nie został uruchomiony z powodu wyłączenia ProfileInstallerInitializer. Pamiętaj, że gdy zgłoszony zostanie ten błąd, w pliku APK aplikacji nadal jest obecny profil. Jeśli nie można znaleźć profilu, zwracany jest kod błędu RESULT_CODE_ERROR_NO_PROFILE_EMBEDDED.
RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION
W pliku APK lub AAB znaleziono profil i został on umieszczony w kole do skompilowania. Gdy profil zostanie zainstalowany przez ProfileInstaller, zostanie umieszczony w kolejce do skompilowania następnym razem, gdy system wykona optymalizację DEX w tle. Profil nie jest aktywny, dopóki kompilacja nie zostanie ukończona. Nie próbuj porównywać profili bazowych, dopóki kompilacja nie zostanie ukończona. Może być konieczne wymuszenie kompilacji profili bazowych. Ten błąd nie wystąpi, jeśli aplikacja zostanie zainstalowana ze sklepu z aplikacjami lub menedżera pakietów na urządzeniu z Androidem 9 (interfejs API 28) lub nowszym, ponieważ kompilacja jest wykonywana podczas instalacji.
RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING
Zainstalowany jest niepasujący profil, a aplikacja została skompilowana z jego użyciem. Jest to wynik instalacji za pomocą Sklepu Google Play lub menedżera pakietów. Ten wynik różni się od RESULT_CODE_COMPILED_WITH_PROFILE, ponieważ profil niezgodny z modelem bazowym zawiera tylko metody, które są wspólne dla profilu i aplikacji. Profil jest więc mniejszy niż oczekiwano, a w jego przypadku zostanie skompilowanych mniej metod niż w przypadku profilu bazowego.
RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE
ProfileVerifier nie może zapisać pliku pamięci podręcznej z wynikiem weryfikacji. Może się tak zdarzyć, jeśli wystąpił problem z uprawnieniami folderu aplikacji lub jeśli na urządzeniu jest za mało wolnego miejsca na dysku.
RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION
ProfileVerifieris running on an unsupported API version of Android. ProfileVerifier obsługuje tylko Androida 9 (poziom interfejsu API 28) i nowszych wersji.
RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST
Podczas wysyłania zapytania do pakietu aplikacji PackageManager wystąpi błąd PackageManager.NameNotFoundException. To powinno się zdarzać rzadko. Spróbuj odinstalować aplikację i wszystko ponownie zainstalować.
RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ
Istnieje plik pamięci podręcznej z poprzednimi wynikami weryfikacji, ale nie można go odczytać. Nie powinno się to zdarzać zbyt często. Spróbuj odinstalować aplikację i wszystko ponownie zainstalować.

Korzystanie z narzędzia ProfileVerifier w wersji produkcyjnej

W środowisku produkcyjnym możesz używać ProfileVerifier w połączeniu z bibliotekami raportowania analitycznego, takimi jak Google Analytics dla Firebase, aby generować zdarzenia analityczne wskazujące stan profilu. Dzięki temu możesz na przykład szybko otrzymywać powiadomienia, gdy zostanie opublikowana nowa wersja aplikacji, która nie zawiera profili bazowych.

Wymuszanie kompilacji profili podstawowych

Jeśli stan kompilacji profili podstawowych to RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION, możesz wymusić natychmiastową kompilację, używając adb:

adb shell cmd package compile -r bg-dexopt PACKAGE_NAME

Sprawdzanie stanu kompilacji bez narzędzia ProfileVerifier

Jeśli nie używasz ProfileVerifier, możesz sprawdzić stan kompilacji za pomocą adb, ale nie uzyskasz tak szczegółowych informacji jak w przypadku ProfileVerifier:

adb shell dumpsys package dexopt | grep -A 2 PACKAGE_NAME

Użycie adb spowoduje wyświetlenie czegoś podobnego do tego:

  [com.google.samples.apps.nowinandroid.demo]
    path: /data/app/~~dzJiGMKvp22vi2SsvfjkrQ==/com.google.samples.apps.nowinandroid.demo-7FR1sdJ8ZTy7eCLwAnn0Vg==/base.apk
      arm64: [status=speed-profile] [reason=bg-dexopt] [primary-abi]
        [location is /data/app/~~dzJiGMKvp22vi2SsvfjkrQ==/com.google.samples.apps.nowinandroid.demo-7FR1sdJ8ZTy7eCLwAnn0Vg==/oat/arm64/base.odex]

Wartość stanu wskazuje stan kompilacji profilu i może przyjmować jedną z tych wartości:

Stan kompilacji Znaczenie
speed‑profile Kompilowany profil istnieje i jest używany.
verify Nie ma skompilowanego profilu.

Stan verify nie oznacza, że plik APK lub AAB nie zawiera profilu, ponieważ może on zostać umieszczony w kole do skompilowania przez następne zadanie optymalizacji DEX w tle.

Wartość powodu określa, co powoduje skompilowanie profilu. Może to być jedna z tych wartości:

Przyczyna Znaczenie
install‑dm Profil bazowy został skompilowany ręcznie lub przez Google Play po zainstalowaniu aplikacji.
bg‑dexopt Profil został skompilowany, gdy urządzenie było nieaktywne. Może to być profil podstawowy lub profil zebrany podczas korzystania z aplikacji.
cmdline Kompilacja została uruchomiona za pomocą adb. Może to być profil podstawowy lub profil zebrany podczas korzystania z aplikacji.

problemy z wydajnością,

W tej sekcji znajdziesz sprawdzone metody prawidłowego definiowania i porównywania profili podstawowych, aby w pełni z nich korzystać.

Prawidłowo porównywać dane o rozruchu

Profile podstawowe będą skuteczniejsze, jeśli dane dotyczące startupów są dobrze zdefiniowane. Te 2 kluczowe dane to czas do początkowego wyświetlenia (TTID)czas do pełnego wyświetlenia (TTFD).

TTID to moment, w którym aplikacja wyświetla pierwszą klatkę. Ważne, aby ten komunikat był jak najkrótszy, ponieważ wyświetlanie czegokolwiek informuje użytkownika, że aplikacja działa. Możesz nawet wyświetlić nieokreślony wskaźnik postępu, aby pokazać, że aplikacja reaguje na działania użytkownika.

TTFD to czas, w którym można wchodzić w interakcję z aplikacją. Pamiętaj, aby trzymać się jak najkrótszych formuł, aby nie irytować użytkowników. Jeśli prawidłowo sygnalizujesz TTFD, informujesz system, że kod, który jest uruchamiany na drodze do TTFD, jest częścią uruchamiania aplikacji. W efekcie system z większym prawdopodobieństwem umieści ten kod w profilu.

Aby zwiększyć responsywność aplikacji, utrzymuj wartości TTID i TTFD na jak najniższym poziomie.

System może wykrywać identyfikatory TTID, wyświetlać je w Logcat i zgłaszać jako część danych porównawczych dotyczących uruchamiania. System nie jest jednak w stanie określić TTFD, dlatego to aplikacja musi zgłaszać, kiedy osiągnie stan interaktywny. Możesz to zrobić, dzwoniąc pod numer reportFullyDrawn() lub ReportDrawn, jeśli używasz Jetpack Compose. Jeśli masz kilka zadań w tle, które muszą zostać ukończone, zanim aplikacja zostanie uznana za całkowicie wycofaną, możesz użyć FullyDrawnReporter, jak opisano w artykule Poprawianie dokładności czasu uruchamiania.

Profile bibliotek i profile niestandardowe

Podczas porównywania wpływu profili może być trudno oddzielić korzyści wynikające z profili aplikacji od profili udostępnionych przez biblioteki, takie jak biblioteki Jetpacka. Gdy kompilujesz plik APK, wtyczka Androida do obsługi Gradle dodaje wszystkie profile w bibliotekach zależnych, a także Twój profil niestandardowy. Jest to dobre rozwiązanie do optymalizacji ogólnej wydajności i zalecane w przypadku wersji do wydania. Utrudnia to jednak pomiar dodatkowego wzrostu skuteczności wynikającego z profilu niestandardowego.

Szybki sposób na ręczne sprawdzenie dodatkowej optymalizacji zapewnianej przez profil niestandardowy to jego usunięcie i wykonywanie testów porównawczych. Następnie zastąp go i ponownie uruchom testy porównawcze. Porównanie tych dwóch zestawień pozwoli Ci zobaczyć, jaką optymalizację zapewniają same profile biblioteki i jakie zapewniają profile biblioteki oraz Twój profil niestandardowy.

Automatyczny sposób porównywania profili polega na utworzeniu nowej wersji, która zawiera tylko profile biblioteki, a nie profil niestandardowy. Porównaj wyniki testów porównawczych z tego wariantu z wariantem opublikowanym, który zawiera zarówno profile biblioteki, jak i Twoje profile niestandardowe. Ten przykład pokazuje, jak skonfigurować wariant, który obejmuje tylko profile bibliotek. Dodaj nową odmianę o nazwie releaseWithoutCustomProfile do modułu konsumenta profilu, który jest zwykle modułem aplikacji:

Kotlin

android {
  ...
  buildTypes {
    ...
    // Release build with only library profiles.
    create("releaseWithoutCustomProfile") {
      initWith(release)
    }
    ...
  }
  ...
}
...
dependencies {
  ...
  // Remove the baselineProfile dependency.
  // baselineProfile(project(":baselineprofile"))
}

baselineProfile {
  variants {
    create("release") {
      from(project(":baselineprofile"))
    }
  }
}

Groovy

android {
  ...
  buildTypes {
    ...
    // Release build with only library profiles.
    releaseWithoutCustomProfile {
      initWith(release)
    }
    ...
  }
  ...
}
...
dependencies {
  ...
  // Remove the baselineProfile dependency.
  // baselineProfile ':baselineprofile"'
}

baselineProfile {
  variants {
    release {
      from(project(":baselineprofile"))
    }
  }
}

Poprzedni przykład kodu usuwa zależność baselineProfile ze wszystkich wariantów i selektywnie stosuje ją tylko do wariantu release. Może się wydawać nielogiczne, że profile bibliotek są nadal dodawane, gdy usuniesz zależność od modułu producenta profili. Ten moduł odpowiada jednak tylko za wygenerowanie profilu niestandardowego. Wtyczka Android Gradle jest nadal używana we wszystkich wariantach i odpowiada za uwzględnianie profili bibliotek.

Musisz też dodać nowy wariant do modułu generatora profili. W tym przykładzie moduł producenta ma nazwę :baselineprofile.

Kotlin

android {
  ...
    buildTypes {
      ...
      // Release build with only library profiles.
      create("releaseWithoutCustomProfile") {}
      ...
    }
  ...
}

Groovy

android {
  ...
    buildTypes {
      ...
      // Release build with only library profiles.
      releaseWithoutCustomProfile {}
      ...
    }
  ...
}

Podczas uruchamiania testu porównawczego w Android Studio wybierz wariant releaseWithoutCustomProfile, aby mierzyć wydajność tylko z profilami biblioteki, lub wariant release, aby mierzyć wydajność z profilami biblioteki i profilami niestandardowymi.

Unikaj uruchamiania aplikacji z dostępem do I/O

Jeśli podczas uruchamiania aplikacja wykonuje wiele wywołań we/wy lub wywołań sieciowych, może to negatywnie wpłynąć zarówno na czas uruchamiania aplikacji, jak i na dokładność testów porównawczych czasu uruchamiania. Te obciążające wywołania mogą trwać nieokreślony czas, który może się zmieniać w czasie, a nawet między iteracjami tego samego testu porównawczego. Wywołania I/O są zazwyczaj lepsze niż wywołania sieciowe, ponieważ na te ostatnie mogą wpływać czynniki zewnętrzne i właściwości samego urządzenia. Unikaj wywołań sieci podczas uruchamiania. Jeśli użycie jednej z tych opcji jest nieuniknione, użyj I/O.

Zalecamy, aby architektura aplikacji obsługiwała uruchamianie aplikacji bez wywołań sieciowych ani wejść/wyjść, nawet jeśli ma to służyć tylko do porównywania czasu uruchamiania. Dzięki temu uzyskasz możliwie najmniejszą zmienność między różnymi iteracjami wartości referencyjnych.

Jeśli Twoja aplikacja korzysta z Hilt, możesz podać fałszywe implementacje ograniczone przez I/O podczas testowania porównawczego w Microbenchmark i Hilt.

Obejmowanie wszystkich ważnych ścieżek użytkownika

Podczas generowania profilu podstawowego musisz dokładnie uwzględnić wszystkie ważne ścieżki użytkowników. Profile podstawowe nie poprawiają ścieżek użytkowników, które nie są uwzględnione. Najskuteczniejsze profile bazowe obejmują wszystkie typowe ścieżki użytkownika podczas uruchamiania aplikacji oraz ścieżki użytkownika w aplikacji, które mają wpływ na wydajność, np. przewijanie list.