Triggerbasiertes Profiling

Unter Android 16 (API-Level 36) und höher können Sie mit ProfilingManager Profile automatisch als Reaktion auf Systemtrigger erfassen, z. B. ANR-Fehler (App antwortet nicht), Abstürze aufgrund von Speichermangel (OutOfMemoryError) oder App-Start-Ereignisse. Da Sie nicht vorhersagen können, wann viele dieser Ereignisse im Feld auftreten, ist es unpraktisch, eine Aufzeichnung im Voraus manuell zu starten. Bei der triggerbasierten Profilerstellung verwaltet das System die Erfassung und liefert das resultierende Profil an Ihre Anwendung, sodass Sie Folgendes tun können:

  • Stabilitätsprobleme beheben: Unvorhersehbare ANRs und Speicherlecks diagnostizieren.
  • Critical User Journeys (CUJs) optimieren: Analysieren Sie Abläufe wie den App-Start (TRIGGER_TYPE_COLD_START und TRIGGER_TYPE_APP_FULLY_DRAWN).
  • Nutzerverhalten analysieren: Sie können Ereignisse wie von Nutzern initiierte App-Beendigungen prüfen.

Eine vollständige Liste der verfügbaren Trigger finden Sie unter ProfilingTrigger.

Arten von Triggern

Trigger unterscheiden sich darin, wie das System das Profil erfasst, wenn das Ereignis eintritt.

Trigger, die den Hintergrund-Trace verwenden

Viele Auslöser, z. B. ein ANR-Fehler, sind die Folge eines früheren Problems. Wenn Sie einen Trace erst starten, nachdem das System den ANR-Fehler erkannt hat, wird die Blockierung, die ihn verursacht hat, nicht berücksichtigt. Um die Aktivität vor dem Ereignis zu erfassen, speichern die folgenden Trigger einen Snapshot des aktiven Hintergrund-Trace des Systems. Wenn beim Auftreten des Ereignisses kein Hintergrund-Trace ausgeführt wird, wird kein Profil erfasst:

Trigger, die eine neue Erfassung starten

Die folgenden Trigger hängen nicht vom Hintergrund-Trace ab. Stattdessen startet das System eine neue Profilerfassung, wenn das Ereignis auftritt:

Auslöser, bei denen das Erfassungsverhalten je nach Anomalie variiert

Bei den folgenden Anomalieauslösern hängen der erfasste Profiltyp (Speichern eines Snapshots des aktiven Hintergrund-Trace oder Starten einer neuen Erfassung) und das Verhalten der Ratenbegrenzung von der spezifischen Anomalie ab, die vom System erkannt wurde:

Trigger einrichten

Der folgende Code zeigt, wie Sie sich für den Trigger TRIGGER_TYPE_APP_FULLY_DRAWN registrieren und eine Wartezeit dafür festlegen.

Kotlin

@RequiresApi(Build.VERSION_CODES.BAKLAVA)
fun recordWithTrigger() {
    val profilingManager =
        applicationContext.getSystemService(ProfilingManager::class.java) ?: return

    val triggers = ArrayList<ProfilingTrigger>()

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

    triggers.add(triggerBuilder.build())

    val callbackExecutor: 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(callbackExecutor, resultCallback)
    profilingManager.addProfilingTriggers(triggers)

    Choreographer.getInstance().postFrameCallback { frameTimeNanos ->
        // This will cause the TRIGGER_TYPE_APP_FULLY_DRAWN to be emitted.
        reportFullyDrawn()
    }
}

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 callbackExecutor = 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(callbackExecutor, resultCallback);
  profilingManager.addProfilingTriggers(triggers);

  Choreographer.getInstance().postFrameCallback((f) -> {
    // This will cause the TRIGGER_TYPE_APP_FULLY_DRAWN to be emitted.
    reportFullyDrawn();
  });
}

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 während eines aktivitätsinitiierten Kaltstarts reportFullyDrawn() aufruft, um zu melden, dass sie gestartet wurde und interaktiv ist.
  3. Trigger-Cooldown festlegen: Wendet zusätzlich zur System-Ratenbegrenzung eine einstündige Wartefrist auf diesen bestimmten Trigger an, indem setRateLimitingPeriodHours verwendet wird. Diese Wartezeit 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: Postet einen Choreographer-Frame-Callback, der reportFullyDrawn() aufruft, sobald der nächste Frame gezeichnet wird. Dadurch wird das Ereignis TRIGGER_TYPE_APP_FULLY_DRAWN an das System gesendet, wodurch das Erfassen von Profilen ausgelöst wird, sofern ein System-Hintergrund-Trace ausgeführt wurde und ein Kontingent für die Ratenbegrenzung verfügbar ist. Damit dieser Trigger ausgelöst wird, muss Ihre App reportFullyDrawn() aufrufen.

Lebenszyklus von Triggern

Registrierte Trigger bleiben auch nach einem Neustart der App oder des Systems erhalten. Sie müssen Trigger nicht bei jedem Start Ihrer App oder nach einem Systemneustart neu registrieren. Sobald Sie Trigger mit addProfilingTriggers registriert haben, werden sie vom System automatisch für Ihre App verwaltet, bis Sie sie entfernen oder die App deinstalliert wird.

Wann die Registrierung von Triggern aufgehoben werden sollte

Da registrierte Trigger auch nach einem Neustart der App oder des Systems erhalten bleiben, werden Trigger, die in einer vorherigen Sitzung registriert wurden, nicht gestoppt, wenn addProfilingTriggers bei nachfolgenden Starts übersprungen wird. Wenn Sie die Erfassung von Profilen für bestimmte Ereignisse oder für alle Ereignisse beenden möchten, rufen Sie explizit removeProfilingTriggersByType oder clearProfilingTriggers auf.

In den folgenden Situationen werden Trigger abgemeldet:

  • Targeting von Kohorten oder Rollback von Remote-Flags: Sie können die Profilierung über Kohorten hinweg mithilfe von Remote-Konfiguration oder A/B-Test-Flags segmentieren. Wenn ein Gerät die Stichprobenkohorte verlässt oder ein Flag deaktiviert wird, melden Sie die Trigger ab.
  • Debugging-Kampagne beenden: Sobald Sie genügend Profile erfasst haben, um ein Problem zu diagnostizieren oder eine Fehlerbehebung bereitzustellen, entfernen Sie den Trigger. Wenn Sie nicht benötigte Trigger abmelden, sparen Sie Gerätespeicher und Kontingent für die Ratenbegrenzung Ihrer App für andere Profilerstellungsanfragen.

Callback-Lebenszyklus

Im Gegensatz zu direkten Profilerstellungsanfragen liefern triggerbasierte Profile nur Ergebnisse an einen globalen Listener, der bei registerForAllProfilingResults registriert ist. Rufen Sie in Ihrem Callback ProfilingResult.getTriggerType auf, um zu ermitteln, welcher Trigger die Erfassung initiiert hat, und rufen Sie ProfilingResult.getResultFilePath auf, um die gespeicherte Profildatei zu finden. Weitere Informationen zu Dateinamen von Triggerprofilen, zum Abrufen von Dateien mit adb und zum Hochladen von Ergebnissen im Hintergrund finden Sie unter Profile abrufen und hochladen.

Trigger bleiben auch nach einem Neustart der App oder des Systems erhalten, der Ergebnis-Callback jedoch nicht. Registrieren Sie Ihren Callback in einem Hintergrund-Executor, nachdem die App gestartet wurde, damit der Kaltstart nicht verlangsamt wird. So erhalten Sie Profile in den beiden folgenden Szenarien:

  • Während Ihre App ausgeführt wird: Bei Triggern, die Ihre App nicht beenden oder zum Absturz bringen, ruft das System Ihren Callback im angegebenen Executor auf, wenn der Trigger auftritt und das Profil gespeichert wird.
  • Nach dem Beenden der App: Bei Triggern, die häufig mit dem Beenden der App verbunden sind oder diesem vorausgehen (z. B. TRIGGER_TYPE_ANR, TRIGGER_TYPE_OOM oder TRIGGER_TYPE_KILL_RECENTS), oder wenn Ihre App vor dem Abschluss der Erfassung beendet wird, speichert das System das Profil im Hintergrund und stellt das Ergebnis in die Warteschlange, damit es nach einem Neustart der App oder des Systems verfügbar ist. Das System speichert nicht zugestellte Ergebnisse in der Warteschlange bis zu 7 Tage lang und stellt sie beim nächsten Ausführen der App und Aufrufen von registerForAllProfilingResults zu.

So funktioniert die Hintergrundverfolgung

Um den Profilerstellungsaufwand mit der Erfassung nützlicher Artefakte in Einklang zu bringen, führt das System Hintergrund-Traces nicht kontinuierlich aus. Stattdessen werden regelmäßig Stichproben genommen, indem Hintergrund-Traces in zufälligen Intervallen gestartet werden.

Vom System ausgelöste Hintergrund-Traces verwenden einen Ringpuffer mit einer systemdefinierten maximalen Größe. Wenn der Zwischenspeicher voll ist, werden die ältesten Daten durch neue Trace-Daten überschrieben. Wie in Abbildung 1 dargestellt, speichert das System den Ringpuffer-Snapshot mit den letzten Aktivitäten, die zum Trigger geführt haben, wenn ein registrierter Trigger ausgelöst wird, während ein Hintergrund-Trace aktiv ist und ein Kontingent für die Ratenbegrenzung verfügbar ist.

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

Auslöserspezifische Wartezeit festlegen

Bei Triggern mit hoher Häufigkeit kann das Kontingent für die Ratenbegrenzung Ihrer App schnell aufgebraucht sein. Um zu verhindern, dass ein einzelner Triggertyp Ihr Kontingent aufbraucht, können Sie mit setRateLimitingPeriodHours zusätzlich zur systemweiten Ratenbegrenzung eine Mindestwartezeit pro Trigger hinzufügen.

Trigger lokal debuggen

Da Hintergrund-Traces in der Produktion in zufälligen Intervallen ausgeführt werden, müssen Sie zum lokalen Testen von Triggern einen kontinuierlichen Hintergrund-Trace erzwingen und die Ratenbegrenzung auf Systemebene für Ihr Paket umgehen. Alle pro Trigger festgelegten Cooldowns mit setRateLimitingPeriodHours gelten weiterhin und rate_limiter.disabled gilt nur für App-gesteuerte requestProfiling-Aufrufe.

Informationen zu den adb-Befehlen, mit denen Sie Hintergrund-Traces erzwingen, Trigger über die Befehlszeile simulieren und den Dienststatus prüfen können, finden Sie unter Hintergrund-Traces für Triggertests erzwingen in Debugging-Befehle für lokales Profiling.