ProfilingManager unterstützt das Erfassen von Profilen basierend auf Systemtriggern. Das System verwaltet den Aufzeichnungsprozess und stellt das resultierende Profil für Ihre App bereit.
Trigger sind an leistungskritische Ereignisse gebunden. Systemaufgezeichnete Profile enthalten detaillierte Debugging-Informationen für die kritischen Nutzeraktionen (Critical User Journeys, CUJs), die mit diesen Triggern verknüpft sind.
Verlaufsdaten erfassen
Für viele Trigger ist es erforderlich, die historischen Daten zu analysieren, 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 Auslösen 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 ausgelöst werden. Daher kann ein Profil nicht manuell im Voraus gestartet werden.
Vorteile der triggerbasierten Erfassung
Der Hauptgrund für die Verwendung von Profiling-Triggern ist die Erfassung von Daten für unvorhersehbare Ereignisse, bei denen eine App nicht manuell mit der Aufzeichnung beginnen kann, bevor diese Ereignisse eintreten. Mit Profiling-Triggern können Sie:
- Leistungsprobleme beheben:ANRs, Speicherlecks und andere Stabilitätsprobleme diagnostizieren.
- Kritische User Journeys optimieren:Analysieren und verbessern Sie Abläufe, z. B. den App-Start.
- Nutzerverhalten analysieren:Sie erhalten Einblicke in Ereignisse wie vom Nutzer 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 die folgenden Schritte aus:
- Get the manager: Ruft den
ProfilingManager-Dienst ab. - Trigger definieren: Erstellt einen
ProfilingTriggerfürTRIGGER_TYPE_APP_FULLY_DRAWN. Dieses Ereignis tritt auf, wenn die App meldet, dass sie gestartet wurde und interaktiv ist. - 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. - Listener registrieren: Ruft
registerForAllProfilingResultsauf, um den Callback zu definieren, der das Ergebnis verarbeitet. Dieser Callback empfängt den Pfad des gespeicherten Profils übergetResultFilePath(). - Trigger hinzufügen: Registriert die Triggerliste mit
ProfilingManagerüberaddProfilingTriggers. - Ereignis auslösen: Ruft
reportFullyDrawn()auf, wodurch das EreignisTRIGGER_TYPE_APP_FULLY_DRAWNan das System gesendet wird. Dadurch wird eine Profilerstellung 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 gezeigt, da Ihre AppreportFullyDrawn()für diesen Trigger aufrufen muss.
Trace abrufen
Das System speichert triggerbasierte Profile im selben Verzeichnis wie andere Profile. Der Dateiname für ausgelöste Traces hat dieses 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, sieht das so aus:
adb pull /data/user/0/com.example.sampleapp/files/profiling/profile_trigger_1_2025-05-06-14-12-40.perfetto-trace
Weitere Informationen zum Visualisieren dieser Traces finden Sie unter Profiling-Daten abrufen und analysieren.
So funktioniert die Hintergrundverfolgung
Um Daten aus der Zeit vor einem Trigger zu erfassen, startet das Betriebssystem regelmäßig einen Hintergrund-Trace. Wenn ein Trigger ausgelöst wird, 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 Auslöser geführt haben.
Sobald das Profil gespeichert ist, benachrichtigt das System Ihre App über den Callback, der für registerForAllProfilingResults bereitgestellt wurde. Dieser Callback gibt den Pfad zum erfassten Profil an, auf das durch Aufrufen von ProfilingResult#getResultFilePath() zugegriffen werden kann.
Um die Auswirkungen auf die Geräteleistung und die Akkulaufzeit zu minimieren, werden Hintergrund-Traces nicht kontinuierlich ausgeführt. Stattdessen wird eine Stichprobenmethode verwendet. Das System startet innerhalb eines festgelegten Zeitrahmens (mit einer Mindest- und Höchstdauer) zufällig einen Hintergrund-Trace. Durch die zufällige Verteilung dieser Traces wird die Triggerabdeckung verbessert.
Systemgesteuerte Profile haben eine vom System definierte maximale Größe und verwenden daher einen Ringpuffer. Wenn der Puffer voll ist, werden die ältesten Daten durch neue Tracedaten überschrieben. Wie in Abbildung 1 dargestellt, deckt ein erfasster Trace möglicherweise nicht die gesamte Dauer der Hintergrundaufzeichnung ab, wenn der Puffer voll ist. Stattdessen wird die letzte Aktivität vor dem Trigger dargestellt.
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 triggerspezifische Ratenbegrenzungen implementieren.
ProfilingManager unterstützt die app-definierte trigger-spezifische Ratenbegrenzung. So können Sie zusätzlich zum vorhandenen Ratenbegrenzer 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 Sperrzeit können Sie sie wieder 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 für Tests 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 der Ratenbegrenzer dies zulässt.
Sie können auch andere Debugging-Optionen aktivieren, z. B. die Ratenbegrenzung deaktivieren, wenn Sie lokal debuggen. Weitere Informationen finden Sie unter Debug-Befehle für lokales Profiling.