Fehlerbehebung bei Referenzprofilen

Dieses Dokument enthält Best Practices für die Diagnose von Problemen und die korrekte Funktionsweise Ihrer Baseline-Profile.

Build-Probleme

Wenn Sie das Beispiel für Baseline-Profile in die Beispiel-App Now in Android kopiert haben, können während der Baseline-Profilaufgabe Testfehler auftreten, die angeben, dass die Tests nicht in einem Emulator ausgeführt werden können:

./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):
        ...

Die Fehler treten auf, weil Now in Android ein von Gradle verwaltetes Gerät zum Generieren des Baseline-Profils verwendet. Die Fehler sind zu erwarten, da Sie in der Regel keine Leistungsvergleiche auf einem Emulator ausführen sollten. Da Sie beim Generieren von Baseline-Profilen jedoch keine Leistungsmesswerte erfassen, können Sie die Erhebung von Baseline-Profilen der Einfachheit halber für Emulatoren ausführen. Wenn Sie Baseline-Profile mit einem Emulator verwenden möchten, führen Sie den Build und die Installation über die Befehlszeile aus und legen Sie ein Argument fest, um die Regeln für Baseline-Profile zu aktivieren:

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

Alternativ können Sie eine benutzerdefinierte Ausführungskonfiguration in Android Studio erstellen, um Referenzprofile für Emulatoren zu aktivieren. Wählen Sie dazu Ausführen > Konfigurationen bearbeiten aus:

Benutzerdefinierte Ausführungskonfiguration zum Erstellen von Referenzprofilen in „Jetzt“ in Android hinzufügen
Abbildung 1: Fügen Sie eine benutzerdefinierte Ausführungskonfiguration hinzu, um in „Jetzt in Android“ Referenzprofile zu erstellen.

Installationsprobleme

Prüfen Sie, ob das APK oder AAB, das Sie erstellen, aus einer Build-Variante stammt, die Baseline-Profile enthält. Am einfachsten können Sie dies prüfen, indem Sie das APK in Android Studio öffnen, indem Sie Build > APK analysieren auswählen, Ihr APK öffnen und in der Datei /assets/dexopt/baseline.prof nach dem Profil suchen:

Mit dem APK-Viewer in Android Studio nach einem Baseline-Profil suchen
Abbildung 2: Suche mit dem APK Viewer in Android Studio nach einem Baseline-Profil.

Referenzprofile müssen auf dem Gerät kompiliert werden, auf dem die App ausgeführt wird. Sowohl bei App-Store-Installationen als auch bei Apps, die mit PackageInstaller installiert wurden, erfolgt die Kompilierung auf dem Gerät bei der App-Installation. Wenn die App jedoch aus Android Studio per Sideload übertragen wird oder Befehlszeilentools verwendet, ist die Jetpack-Bibliothek ProfileInstaller dafür verantwortlich, die Profile zur Kompilierung während der nächsten DEX-Optimierung im Hintergrund in die Warteschlange zu stellen. Wenn Sie in diesen Fällen sicherstellen möchten, dass Ihre Referenzprofile verwendet werden, müssen Sie möglicherweise die Kompilierung von Referenzprofilen erzwingen. Mit ProfileVerifier können Sie den Status der Profilinstallation und -kompilierung abfragen, wie im folgenden Beispiel gezeigt:

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);
    }
}

Die folgenden Ergebniscodes enthalten Hinweise für die Ursache einiger Probleme:

RESULT_CODE_COMPILED_WITH_PROFILE
Das Profil wird installiert, kompiliert und immer dann verwendet, wenn die App ausgeführt wird. Dies ist das Ergebnis, das Sie sehen möchten.
RESULT_CODE_ERROR_NO_PROFILE_EMBEDDED
Im ausgeführten APK oder AAB wurde kein Profil gefunden. Achten Sie darauf, dass Sie eine Build-Variante verwenden, die Baseline-Profile enthält, wenn dieser Fehler angezeigt wird, und dass das APK ein Profil enthält.
RESULT_CODE_NO_PROFILE
Bei der Installation der Anwendung über den App-Shop oder Paketmanager wurde für diese App kein Profil installiert. Der Hauptgrund für diesen Fehlercode ist, dass das Profilinstallationsprogramm nicht ausgeführt wurde, weil ProfileInstallerInitializer deaktiviert war. Hinweis: Wenn dieser Fehler gemeldet wird, wurde weiterhin ein eingebettetes Profil im App-APK gefunden. Wenn kein eingebettetes Profil gefunden wird, wird als Fehlercode RESULT_CODE_ERROR_NO_PROFILE_EMBEDDED zurückgegeben.
RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION
Ein Profil wurde im APK oder AAB gefunden und zur Kompilierung in die Warteschlange gestellt. Wenn ein Profil von ProfileInstaller installiert wird, wird es bei der nächsten Ausführung der DEX-Hintergrundoptimierung zur Kompilierung in die Warteschlange gestellt. Das Profil ist erst aktiv, wenn die Kompilierung abgeschlossen ist. Versuchen Sie nicht, ein Benchmarking Ihrer Baseline-Profile durchzuführen, bis die Kompilierung abgeschlossen ist. Möglicherweise müssen Sie die Kompilierung von Referenzprofilen erzwingen. Dieser Fehler tritt nicht auf, wenn eine App aus dem App-Shop oder Paketmanager auf Geräten mit Android 9 (API 28) und höher installiert wird, da die Kompilierung während der Installation erfolgt.
RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING
Ein nicht übereinstimmendes Profil wurde installiert und die App wurde damit kompiliert. Dies ist das Ergebnis der Installation über den Google Play Store oder den Paketmanager. Beachten Sie, dass sich dieses Ergebnis von RESULT_CODE_COMPILED_WITH_PROFILE unterscheidet, da das nicht übereinstimmende Profil nur Methoden kompiliert, die noch zwischen dem Profil und der Anwendung gemeinsam genutzt werden. Das Profil ist effektiv kleiner als erwartet und es werden weniger Methoden kompiliert, als im Baseline-Profil enthalten waren.
RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE
ProfileVerifier kann die Cache-Datei mit den Bestätigungsergebnissen nicht schreiben. Dies kann entweder an den Berechtigungen für die App-Ordner liegen oder daran, dass nicht genügend freier Speicherplatz auf dem Gerät vorhanden ist.
RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION
ProfileVerifieris running on an unsupported API version of Android. ProfileVerifier unterstützt nur Android 9 (API-Level 28) und höher.
RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST
Bei der Abfrage von PackageManager für das App-Paket wird ein PackageManager.NameNotFoundException ausgegeben. Das sollte nur selten vorkommen. Deinstallieren Sie die App und installieren Sie sie noch einmal neu.
RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ
Eine frühere Cache-Datei mit Überprüfungsergebnissen ist vorhanden, kann aber nicht gelesen werden. Das sollte nur selten vorkommen. Deinstallieren Sie die App und installieren Sie alles neu.

ProfileVerifier in der Produktionsumgebung verwenden

In Produktionsversionen können Sie ProfileVerifier zusammen mit Analysebibliotheken wie Google Analytics for Firebase verwenden, um Analyseereignisse zu generieren, die den Profilstatus angeben. So werden Sie beispielsweise schnell benachrichtigt, wenn eine neue Anwendungsversion veröffentlicht wird, die keine Referenzprofile enthält.

Kompilierung von Referenzprofilen erzwingen

Wenn der Kompilierungsstatus Ihrer Referenzprofile RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION lautet, können Sie die sofortige Kompilierung mit adb erzwingen:

adb shell cmd package compile -r bg-dexopt PACKAGE_NAME

Kompilierungsstatus ohne ProfileVerifier prüfen

Wenn Sie ProfileVerifier nicht verwenden, können Sie den Kompilierungsstatus mit adb prüfen, auch wenn er weniger detaillierte Informationen wie ProfileVerifier liefert:

adb shell dumpsys package dexopt | grep -A 2 PACKAGE_NAME

Die Verwendung von adb führt in etwa so:

  [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]

Der Statuswert gibt den Status der Profilkompilierung an und ist einer der folgenden Werte:

Kompilierungsstatus Bedeutung
speed‑profile Ein kompiliertes Profil ist vorhanden und wird verwendet.
verify Es ist kein kompiliertes Profil vorhanden.

Der Status verify bedeutet nicht, dass das APK oder AAB kein Profil enthält, da es zur Kompilierung durch die nächste DEX-Optimierungsaufgabe im Hintergrund in die Warteschlange gestellt werden kann.

Der Wert reason (Grund) gibt an, wodurch die Kompilierung des Profils ausgelöst wird. Er ist einer der folgenden Werte:

Grund Bedeutung
install‑dm Ein Baseline-Profil wurde manuell oder von Google Play bei der Installation der App kompiliert.
bg‑dexopt Während Ihr Gerät inaktiv war, wurde ein Profil kompiliert. Dies kann ein Baseline-Profil oder ein Profil sein, das während der Anwendungsnutzung erfasst wurde.
cmdline Die Kompilierung wurde mit ADB ausgelöst. Dies kann ein Baseline-Profil oder ein Profil sein, das während der Anwendungsnutzung erfasst wurde.

Leistungsprobleme

In diesem Abschnitt finden Sie einige Best Practices zum korrekten Definieren und Benchmarken Ihrer Baseline-Profile, damit Sie sie optimal nutzen können.

Start-up-Messwerte korrekt abgleichen

Ihre Baseline-Profile sind effektiver, wenn Ihre Start-up-Messwerte klar definiert sind. Die beiden wichtigsten Messwerte sind Zeit bis zur ersten Anzeige (TTID) und Zeit bis zur vollständigen Anzeige (Time to Full Display, TTFD).

Bei der TTID zeichnet die App den ersten Frame. Es ist wichtig, dies so kurz wie möglich zu halten, da Nutzer durch eine entsprechende Anzeige erkennen können, dass die Anwendung ausgeführt wird. Du kannst sogar eine unbestimmte Fortschrittsanzeige einblenden lassen, um anzuzeigen, dass die App reagiert.

Bei TTFD kann mit der App tatsächlich interagiert werden. Es ist wichtig, ihn so kurz wie möglich zu halten, um Frustration bei den Nutzenden zu vermeiden. Wenn Sie TTFD richtig signalisieren, teilen Sie dem System mit, dass der auf dem Weg zu TTFD ausgeführte Code Teil des Anwendungsstarts ist. Aus diesem Grund ist es wahrscheinlicher, dass das System diesen Code im Profil platziert.

Halten Sie sowohl TTID als auch TTFD so niedrig wie möglich, damit Ihre App responsiv wirkt.

Das System kann TTID erkennen, in Logcat anzeigen und als Teil der Start-Benchmarks melden. Das System kann TTFD jedoch nicht ermitteln und die App muss melden, wenn sie einen vollständig gezeichneten interaktiven Status erreicht. Rufen Sie dazu reportFullyDrawn() oder ReportDrawn auf, wenn Sie Jetpack Compose verwenden. Wenn Sie mehrere Hintergrundaufgaben ausführen müssen, bevor die Anwendung als vollständig gezeichnet gilt, können Sie FullyDrawnReporter verwenden, wie unter Genauigkeit des Startzeitpunkts verbessern beschrieben.

Bibliotheksprofile und benutzerdefinierte Profile

Beim Benchmarking der Auswirkungen von Profilen kann es schwierig sein, die Vorteile der Profile Ihrer Anwendung von den Profilen zu unterscheiden, die von Bibliotheken wie Jetpack-Bibliotheken beigesteuert werden. Wenn du dein APK erstellst, fügt das Android-Gradle-Plug-in alle Profile in den Bibliotheksabhängigkeiten und dein benutzerdefiniertes Profil hinzu. Dies ist gut zur Optimierung der Gesamtleistung und wird für Ihre Release-Builds empfohlen. Es ist jedoch schwierig zu messen, wie viel zusätzliche Leistungssteigerung aus Ihrem benutzerdefinierten Profil resultiert.

Sie können die durch Ihr benutzerdefinierte Profil bereitgestellte zusätzliche Optimierung schnell manuell sehen, indem Sie sie entfernen und Benchmarks ausführen. Ersetzen Sie es dann und führen Sie Ihre Benchmarks noch einmal aus. Wenn Sie die beiden miteinander vergleichen, sehen Sie die Optimierungen, die nur durch die Bibliotheksprofile bereitgestellt werden, sowie die Bibliotheksprofile und Ihr benutzerdefiniertes Profil.

Eine automatisierbare Methode für den Vergleich von Profilen besteht darin, eine neue Build-Variante zu erstellen, die nur die Bibliotheksprofile und nicht Ihr benutzerdefiniertes Profil enthält. Vergleichen Sie Benchmarks dieser Variante mit der Releasevariante, die sowohl die Bibliotheksprofile als auch Ihre benutzerdefinierten Profile enthält. Das folgende Beispiel zeigt, wie Sie die Variante einrichten, die nur Bibliotheksprofile enthält. Fügen Sie dem Nutzermodul Ihres Profils eine neue Variante mit dem Namen releaseWithoutCustomProfile hinzu. Das Nutzermodul ist normalerweise Ihr Anwendungsmodul:

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"))
    }
  }
}

Groovig

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

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

Im vorherigen Codebeispiel wird die baselineProfile-Abhängigkeit aus allen Varianten entfernt und selektiv nur auf die release-Variante angewendet. Es mag unlogisch klingen, dass die Bibliotheksprofile noch hinzugefügt werden, nachdem die Abhängigkeit vom Profilerstellermodul entfernt wurde. Dieses Modul ist jedoch nur für die Generierung Ihres benutzerdefinierten Profils verantwortlich. Das Android-Gradle-Plug-in wird weiterhin für alle Varianten ausgeführt und ist für die Einbindung von Bibliotheksprofilen verantwortlich.

Außerdem müssen Sie die neue Variante dem Modul zur Profilerstellung hinzufügen. In diesem Beispiel heißt das Producer-Modul :baselineprofile.

Kotlin

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

Groovig

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

Führen Sie folgenden Befehl aus, um nur mit den Bibliotheksprofilen zu vergleichen:

./gradlew :baselineprofile:connectedBenchmarkReleaseWithoutCustomProfileAndroidTest

Führen Sie folgenden Befehl aus, um sowohl mit den Bibliotheksprofilen als auch mit Ihrem benutzerdefinierten Profil Benchmarks zu erstellen:

./gradlew :baselineprofile:connectedBenchmarkReleaseAndroidTest

Wenn Sie den obigen Code in der MacroBenchmark-Beispielanwendung ausführen, sehen Sie einen Leistungsunterschied zwischen den beiden Varianten. Nur mit den Bibliotheksprofilen zeigt die warme startupCompose-Benchmark die folgenden Ergebnisse:

SmallListStartupBenchmark_startupCompose[mode=COLD]
timeToInitialDisplayMs   min  70.8,   median  79.1,   max 126.0
Traces: Iteration 0 1 2 3 4 5 6 7 8 9

In vielen Jetpack Compose-Bibliotheken gibt es Bibliotheksprofile, sodass allein mit dem Baseline Profile Gradle-Plug-in einige Optimierungen möglich sind. Bei Verwendung des benutzerdefinierten Profils gibt es jedoch weitere Optimierungen:

SmallListStartupBenchmark_startupCompose[mode=COLD]
timeToInitialDisplayMs   min 57.9,   median 73.5,   max 92.3
Traces: Iteration 0 1 2 3 4 5 6 7 8 9

E/A-gebundenes Starten von Anwendungen vermeiden

Wenn Ihre Anwendung während des Starts viele E/A-Aufrufe oder Netzwerkaufrufe durchführt, kann dies sich sowohl auf die Startzeit der Anwendung als auch auf die Genauigkeit Ihres Start-Benchmarkings auswirken. Diese komplexen Aufrufe können unbestimmte Zeit in Anspruch nehmen, die sich im Laufe der Zeit und sogar zwischen Iterationen derselben Benchmark ändern können. E/A-Aufrufe sind im Allgemeinen besser als Netzwerkaufrufe, da letztere von Faktoren außerhalb des Geräts und auf dem Gerät selbst beeinflusst werden können. Vermeiden Sie Netzwerkaufrufe während des Starts. Wenn sich die Verwendung einer der beiden Methoden nicht vermeiden lässt, verwende E/A.

Wir empfehlen, dass Ihre Anwendungsarchitektur den Anwendungsstart ohne Netzwerk- oder E/A-Aufrufe unterstützt, auch wenn Sie sie nur für das Benchmarking des Start-ups verwenden möchten. So wird die geringstmögliche Variabilität zwischen verschiedenen Iterationen Ihrer Benchmarks sichergestellt.

Wenn Ihre Anwendung Hilt verwendet, können Sie beim Benchmarking in MicroBenchmark und Hilt gefälschte E/A-gebundene Implementierungen bereitstellen.

Alle wichtigen User Journeys abdecken

Es ist wichtig, alle wichtigen Nutzerpfade bei der Erstellung des Baseline-Profils genau abzudecken. Nicht abgedeckte Nutzerpfade werden nicht durch Baseline-Profile verbessert. Die effektivsten Basisprofile umfassen alle gängigen Nutzerpfade bei Start-ups sowie leistungsorientierte In-App-Nutzerpfade wie Scrolllisten.