Triggerbasiertes Profiling

ProfilingManager unterstützt das Erfassen von Profilen basierend auf Systemtriggern. Das System verwaltet den Aufzeichnungsprozess und stellt das resultierende Profil Ihrer App zur Verfügung.

Trigger sind an leistungskritische Ereignisse gebunden. Vom System aufgezeichnete Profile enthalten detaillierte Debugging-Informationen für wichtiges Nutzerverhalten (Critical User Journey, CUJ), das mit diesen Triggern verknüpft ist.

Verlaufsdaten erfassen

Viele Trigger erfordern die Analyse der Verlaufsdaten, die zum Ereignis geführt haben. Der Trigger selbst ist oft eine Folge eines Problems und nicht die Ursache. Wenn Sie ein Profil erst nach dem Auftreten des Triggers starten, kann die Ursache bereits verloren gegangen sein.

Wenn beispielsweise ein Vorgang mit langer Ausführungszeit im UI-Thread ausgeführt wird, führt dies zu einem ANR-Fehler (Application Not Responding). Wenn das System den ANR-Fehler erkennt und die App benachrichtigt, ist der Vorgang möglicherweise bereits abgeschlossen. Wenn Sie ein Profil zu diesem Zeitpunkt starten, wird die eigentliche Blockierung nicht berücksichtigt.

Es ist nicht möglich, genau vorherzusagen, wann bestimmte Trigger auftreten. Daher ist es unmöglich, ein Profil im Voraus manuell zu starten.

Warum sollte ich die triggerbasierte Erfassung verwenden?

Der Hauptgrund für die Verwendung von Profiling-Triggern ist die Erfassung von Daten für unvorhersehbare Ereignisse, bei denen es für eine App unmöglich ist, die Aufzeichnung manuell zu starten, bevor diese Ereignisse auftreten. Profiling-Trigger können für Folgendes verwendet werden:

  • Leistungsprobleme beheben: ANRs, Speicherlecks und andere Stabilitätsprobleme diagnostizieren.
  • Wichtiges Nutzerverhalten optimieren: Analysieren und verbessern Sie Abläufe, z. B. den App-Start.
  • Nutzerverhalten analysieren: Sie erhalten Einblicke in Ereignisse wie von Nutzern initiierte App-Beendigungen.

Trigger einrichten

Der folgende Code zeigt, wie Sie sich für den TRIGGER_TYPE_APP_FULLY_DRAWN-Trigger registrieren und eine Ratenbegrenzung darauf anwenden.

Kotlin

fun recordWithTrigger() {
    val profilingManager = applicationContext.getSystemService(ProfilingManager::class.java)

    val triggers = ArrayList<ProfilingTrigger>()

    val triggerBuilder = ProfilingTrigger.Builder(ProfilingTrigger.TRIGGER_TYPE_APP_FULLY_DRAWN)
        .setRateLimitingPeriodHours(1)

    triggers.add(triggerBuilder.build())

    val mainExecutor: Executor = Executors.newSingleThreadExecutor()

    val resultCallback = Consumer<ProfilingResult> { profilingResult ->
        if (profilingResult.errorCode == ProfilingResult.ERROR_NONE) {
            Log.d(
                "ProfileTest",
                "Received profiling result file=" + profilingResult.resultFilePath
            )
            setupProfileUploadWorker(profilingResult.resultFilePath)
        } else {
            Log.e(
                "ProfileTest",
                "Profiling failed errorcode=" + profilingResult.errorCode + " errormsg=" + profilingResult.errorMessage
            )
        }
    }

    profilingManager.registerForAllProfilingResults(mainExecutor, resultCallback)
    profilingManager.addProfilingTriggers(triggers)

}

Java

public void recordWithTrigger() {
  ProfilingManager profilingManager = getApplicationContext().getSystemService(
      ProfilingManager.class);
  List<ProfilingTrigger> triggers = new ArrayList<>();
  ProfilingTrigger.Builder triggerBuilder = new ProfilingTrigger.Builder(
      ProfilingTrigger.TRIGGER_TYPE_APP_FULLY_DRAWN);
  triggerBuilder.setRateLimitingPeriodHours(1);
  triggers.add(triggerBuilder.build());

  Executor mainExecutor = Executors.newSingleThreadExecutor();
  Consumer<ProfilingResult> resultCallback =
      new Consumer<ProfilingResult>() {
        @Override
        public void accept(ProfilingResult profilingResult) {
          if (profilingResult.getErrorCode() == ProfilingResult.ERROR_NONE) {
            Log.d(
                "ProfileTest",
                "Received profiling result file=" + profilingResult.getResultFilePath());
            setupProfileUploadWorker(profilingResult.getResultFilePath());
          } else {
            Log.e(
                "ProfileTest",
                "Profiling failed errorcode="
                    + profilingResult.getErrorCode()
                    + " errormsg="
                    + profilingResult.getErrorMessage());
          }
        }
      };
  profilingManager.registerForAllProfilingResults(mainExecutor, resultCallback);
  profilingManager.addProfilingTriggers(triggers);

}

Der Code führt folgende Schritte aus:

  1. Manager abrufen: Ruft den ProfilingManager-Dienst ab.
  2. Trigger definieren: Erstellt einen ProfilingTrigger für TRIGGER_TYPE_APP_FULLY_DRAWN. Dieses Ereignis tritt auf, wenn die App meldet, dass sie gestartet wurde und interaktiv ist.
  3. Ratenbegrenzungen festlegen: Wendet eine Ratenbegrenzung von einer Stunde auf diesen bestimmten Trigger (setRateLimitingPeriodHours(1)) an. Dadurch wird verhindert, dass die App mehr als ein Startprofil pro Stunde aufzeichnet.
  4. Listener registrieren: Ruft registerForAllProfilingResults auf, um den Callback zu definieren, der das Ergebnis verarbeitet. Dieser Callback empfängt den Pfad des gespeicherten Profils über getResultFilePath().
  5. Trigger hinzufügen: Registriert die Triggerliste im ProfilingManager über addProfilingTriggers.
  6. Ereignis auslösen: Ruft reportFullyDrawn() auf, wodurch das Ereignis TRIGGER_TYPE_APP_FULLY_DRAWN an das System gesendet wird. Dadurch wird das Erfassen von Profilen ausgelöst, sofern ein System-Hintergrund-Trace ausgeführt wurde und ein Kontingent für die Ratenbegrenzung verfügbar ist. In diesem optionalen Schritt wird ein End-to-End-Ablauf demonstriert, da Ihre App für diesen Trigger reportFullyDrawn() aufrufen muss.

Trace abrufen

Das System speichert triggerbasierte Profile im selben Verzeichnis wie andere Profile. Der Dateiname für ausgelöste Traces hat folgendes Format:

profile_trigger_<profile_type_code>_<datetime>.<profile-type-name>

Sie können die Datei mit ADB abrufen. Wenn Sie beispielsweise den mit dem Beispielcode erfassten System-Trace mit ADB abrufen möchten, könnte das so aussehen:

adb pull /data/user/0/com.example.sampleapp/files/profiling/profile_trigger_1_2025-05-06-14-12-40.perfetto-trace
abrufen.

Weitere Informationen zur Visualisierung dieser Traces finden Sie unter Profiling-Daten abrufen und analysieren.

So funktioniert das Tracing im Hintergrund

Um Daten vor einem Trigger zu erfassen, startet das Betriebssystem regelmäßig einen Hintergrund-Trace. Wenn ein Trigger auftritt, während dieser Hintergrund-Trace aktiv ist, und Ihre App dafür registriert ist, speichert das System das Trace-Profil im Verzeichnis Ihrer App. Das Profil enthält dann Informationen, die zum Trigger geführt haben.

Sobald das Profil gespeichert ist, benachrichtigt das System Ihre App über den Callback, der für registerForAllProfilingResults bereitgestellt wurde. Dieser Callback stellt den Pfad zum erfassten Profil bereit, auf das durch Aufrufen von ProfilingResult#getResultFilePath() zugegriffen werden kann.

Diagramm zur Funktionsweise von Hintergrund-Trace-Snapshots mit einem Ringpuffer, der Daten vor einem Triggerereignis erfasst.
Abbildung 1: Funktionsweise von Hintergrund-Trace-Snapshots

Damit die Geräteleistung und die Akkulaufzeit nicht beeinträchtigt werden, führt das System Hintergrund-Traces nicht kontinuierlich aus. Stattdessen wird eine Stichprobenmethode verwendet. Das System startet innerhalb eines festgelegten Zeitraums (mit einer Mindest- und Höchstdauer) zufällig einen Hintergrund-Trace. Wenn diese Traces zufällig verteilt werden, verbessert sich die Triggerabdeckung.

Vom System ausgelöste Profile haben eine systemdefinierte maximale Größe und verwenden daher einen Ringpuffer. Wenn der Zwischenspeicher voll ist, werden die ältesten Daten durch neue Trace-Daten überschrieben. Wie in Abbildung 1 dargestellt, deckt ein erfasster Trace möglicherweise nicht die gesamte Dauer der Hintergrundaufzeichnung ab, wenn der Zwischenspeicher voll ist. Stattdessen stellt er die letzte Aktivität dar, die zum Trigger geführt hat.

Triggerspezifische Ratenbegrenzung implementieren

Bei Triggern mit hoher Häufigkeit kann das Kontingent für die Ratenbegrenzung Ihrer App schnell aufgebraucht sein. Weitere Informationen zur Ratenbegrenzung finden Sie unter Funktionsweise der Ratenbegrenzung. Um zu verhindern, dass ein einzelner Triggertyp Ihr Kontingent aufbraucht, können Sie triggerbezogene Ratenbegrenzungen implementieren.

ProfilingManager unterstützt App-definierte triggerspezifische Ratenbegrenzungen. So können Sie zusätzlich zur vorhandenen Ratenbegrenzung eine weitere Ebene der zeitbasierten Drosselung hinzufügen. Mit der setRateLimitingPeriodHours API können Sie eine bestimmte Cooldown-Zeit für einen Trigger festlegen. Nach Ablauf der Wartezeit können Sie sie noch einmal auslösen.

Trigger lokal debuggen

Da Hintergrund-Traces zu zufälligen Zeiten ausgeführt werden, ist das lokale Debuggen von Triggern schwierig. Verwenden Sie den folgenden ADB-Befehl, um einen Hintergrund-Trace zu Testzwecken zu erzwingen:

adb shell device_config put profiling_testing system_triggered_profiling.testing_package_name <com.example.myapp>

Mit diesem Befehl wird das System gezwungen, einen kontinuierlichen Hintergrund-Trace für das angegebene Paket zu starten. So kann für jeden Trigger ein Profil erstellt werden, sofern die Ratenbegrenzung dies zulässt.

Sie können auch andere Debugging-Optionen aktivieren, z. B. die Ratenbegrenzung beim lokalen Debugging deaktivieren. Weitere Informationen finden Sie unter Debugging-Befehle für die lokale Profilerstellung.