Für die Optimierung von Android-Apps sind detaillierte Einblicke in gleichzeitige Workflows und das Aussetzen von Koroutinen erforderlich.
Das lokale Profiling bietet zwar einen nützlichen Überblick, aber die Skalierung dieser Informationen auf die Produktion ohne zusätzlichen Aufwand bleibt eine Herausforderung.
In dieser Session wird ein Architekturkonzept für eine kontinuierliche Feedbackschleife zur Leistung vorgestellt.
androidx.tracing 2.0: Back-Ends dynamisch austauschen und Koroutinenkontexte (Perfetto-Abläufe) über Threads hinweg weitergeben.
Intelligente Instrumentierung: Verwenden Sie lokales ART-Tracing, um Hotspots zu finden, und automatisieren Sie dann die Einfügung von Trace-Punkten mit geringem Overhead.
Makro-Benchmarking: Regressionen verfolgen und Leistungssteigerungen auf physischen Geräten in der CI überprüfen.
Feld-Tracing: Mit ProfilingManager können Sie über lokales und CI-Tracing hinausgehen.
traceCoroutine verfolgen oder Propagierungstokens an Callback-basierten Code übergeben.Transkript
Rahul Ravikumar
Hallo zusammen,
Mein Name ist Rahul.
Ich bin Engineer im Android Developer Experience-Team.
Ich konzentriere mich auf die Leistung von Apps und Bibliotheken. In diesem Vortrag geht es um Tracing.
Bevor wir uns mit dem Tracing befassen, möchte ich einige Begriffe klären, damit Sie mir folgen können.
Wenn ich in diesem Vortrag von Tracing spreche, meine ich explizites Tracing, also die manuelle Instrumentierung von Code, damit Sie wissen, was die Anwendung tut.
Ich spreche nicht von automatischen Profilern, auf die ich noch eingehen werde.
Manuelles Tracing ist also die Kunst, „Brotkrümel“ in Ihrer Codebasis zu hinterlassen, damit Sie später, wenn Sie ein Leistungsproblem oder ein anderes Problem haben, die Ursache finden können.
Das ist normalerweise viel besser als Logs.
Ich weiß nicht, ob Sie schon einmal die Logcat-Ausgabe einer großen App gesehen haben.
Es ist normalerweise ein riesiger Textblock, der sich nicht so einfach nachvollziehen lässt.
Tracing ist dagegen viel besser.
Das ist leichter zu visualisieren.
Sie haben Dauern.
Sie haben eine gewisse Struktur.
Sie haben einige Tools, mit denen Sie die Daten sogar aufschlüsseln und genau sehen können, was wann passiert ist.
Das ist also viel besser.
Und Sie können Ihre Agenten auf einen Trace verweisen und Fragen stellen.
Sie können Tools wie Trace Processor und Benchmarks verwenden und Ihre Traces sogar in Messwerte umwandeln.
Das sind alles Dinge, die Sie mit Traces tun können, die mit Logcat nur schwer möglich sind.
Profiling, und ich spreche hier von automatischem Profiling, ist dagegen in der Regel eine Reaktion auf ein bekanntes Problem.
Wenn Sie beispielsweise feststellen, dass Ihre App langsam startet oder etwas nicht richtig funktioniert, können Sie das Problem eingrenzen und herausfinden, was los ist.
Wenn Sie zu diesem Zeitpunkt noch keine manuellen Traces haben, die Ihnen helfen, kommt das automatische Profiling ins Spiel.
Sie können beispielsweise ART-Methoden-Tracing verwenden, bei dem jeder einzelne Methodeneintritt und -austritt instrumentiert wird.
Oder Sie sehen sich Stack-Sampling mit Tools wie Simpleperf an.
Anstatt also jeden einzelnen Methodeneintrag und -ausgang zu verwenden und einen hohen Overhead zu haben, sehen Sie sich Aufrufstapel-Stichproben an und sehen, was sich im Hot Path Ihrer Anwendung befindet.
Wir schreiben das Jahr 2026, RAM ist überall ein Problem, also sehen Sie sich vielleicht den Arbeitsspeicher, Speicherlecks und Heap-Profile an.
Dies ist also ein weiterer Profiler, den Sie nutzen können.
Sie können sich die Heap-Aufbewahrungspfade ansehen.
Sie können sich ansehen, welcher Code Speicher zuweist, und alle Arten von Analysen durchführen, die damit möglich sind.
Genau.
Jetzt haben wir dieses Vokabular.
Sehen wir uns nun an, warum wir eine neue Bibliothek erstellt haben.
Tracing-APIs, die 1.0-APIs, wurden ursprünglich in Android Jelly Bean eingeführt.
Das war 2012.
Das ist über 14 Jahre her.
Seitdem haben sich die APIs nicht so stark weiterentwickelt, wie Sie vielleicht denken.
Wir haben einige Erweiterungen hinzugefügt.
Asynchrone Ereignisse und Zähler wurden in Android 10 hinzugefügt.
Das war 2019.
Und das war es.
Die API wurde seitdem nicht weiterentwickelt.
Das Ökosystem hat sich jedoch stark weiterentwickelt.
Das Plattform-Tracing hat sich also enorm verbessert, aber nichts davon hat sich in Form von API-Ergänzungen auf Sie ausgewirkt.
Das möchten wir ändern.
Das andere große Problem ist, dass das Tracing in 1.0 ein festes Tracing-Backend hat. Das bedeutet, dass jeder Trace-Abschnitt, den Sie Ihrem Code hinzufügen, immer im System-Trace-Zwischenspeicher landet.
Atrace ist das Android-Tracing-Subsystem, das diese Daten besitzt.
Wenn Sie also Ihre Traces in Feldtelemetrie umwandeln möchten, ist das sehr schwierig, weil Sie erst einmal herausfinden müssen, wie atrace diese Daten speichert.
Es ist ein gut dokumentiertes, aber möglicherweise schwer zu konsumierendes Format.
Das ist also etwas, das wir beheben möchten.
Deshalb möchten wir Ihnen die Tools an die Hand geben, mit denen Sie Ihre Tracing-Daten überallhin mitnehmen können.
Außerdem gab es in Tracing 1.0 eine Einschränkung, bei der Labels auf 128 Zeichen begrenzt waren.
Das war 2012 wahrscheinlich eine vernünftige Einschränkung, aber im Jahr 2026 wirkt das ziemlich seltsam.
Erschwerend kommt hinzu, dass Sie Ihrem Trace keine zusätzlichen Metadaten hinzufügen können, was besonders problematisch ist.
Und dann ist da noch der Overhead.
Wenn Sie also Ihre Anwendung tracen, müssen Sie sich des Overheads bewusst sein, denn die Beobachtung eines Systems sollte nicht das ändern, was das System im Hintergrund tut.
Wenn Sie also eine Race-Bedingung reproduzieren möchten, sollte das Tracing die Race-Bedingung nicht verschwinden lassen.
Sie sollten also darauf achten, dass alle Semantiken erhalten bleiben, die Sie sonst verwenden.
Tracing 1.0 hatte einen relativ hohen Overhead, den wir verbessern wollten.
Okay.
Ich habe viel über die Probleme gesprochen.
Sehen wir uns nun einige der Probleme visuell an und wie sie sich in der Praxis auswirken.
Hier sehen wir einen Perfetto-Trace.
Es ist für eine App, com.example.app.
Wir haben einen Hauptthread und ein einzelnes Slice namens initializeComponent.
Idealerweise möchten wir noch mehr Details.
Fügen wir also ein Detail hinzu.
Wir wissen also, dass initializeComponent die Funktion performInitialize aufruft.
Wir wissen immer noch nicht, welche Komponente initialisiert wird.
Das einzige Tool in unserem Toolset mit Tracing 1 ist der Name. Daher fügen wir ihn dem Namen des Slice hinzu.
Denken Sie daran, dass die Namen von Tracing-Slices auf 128 Zeichen beschränkt sind und daher in Ihrem Trace abgeschnitten werden.
Und als wäre das nicht genug, kommt der Aufwand für das Kürzen jetzt auch noch zum Aufwand für das Tracing hinzu.
Sie stoßen also ziemlich schnell auf dieses Problem.
Das wollten wir unbedingt ändern.
Sie haben den Perfetto-Trace gesehen und wissen, wie er visuell aussieht.
Sehen wir uns nun an, wie der Code geschrieben wird, um diese Trace-Abschnitte auszugeben.
Auch hier verwenden wir die initializeComponent API.
Sie verwendet einen Kontext und einen Namen und ruft performInitialize auf.
So weit, so gut
Jetzt möchten wir einen Trace mit der Tracing 1.0 API hinzufügen. Dazu importieren Sie androidx.tracing.trace und verwenden einen Trace-Block.
Sie geben ihm einen Namen.
Hier ist es performInitialize.
Wie zuvor möchten wir den Namen der Komponente wissen. Dazu verwenden wir die String-Interpolation von Kotlin und fügen den Namen hinzu.
Und hier ist das erste Problem.
Hier gilt also die Zeichenbeschränkung auf 128.
Rahul Ravikumar
Mit Tracing 2 können wir das beheben.
Anstelle von androidx.tracing.trace verwenden Sie Tracer.
Die API sieht bemerkenswert ähnlich aus.
Statt trace haben Sie Tracer.global.trace.
Sie haben eine Kategorie.
Wir werden später noch genauer auf Kategorien eingehen, aber im Grunde haben Sie einen Namen.
Und dann haben Sie noch den Codeblock, den Sie verfolgen möchten.
Und jetzt gibt es keine Beschränkung mehr auf 128 Zeichen.
Ich habe über den Tracing-Overhead gesprochen.
Ich möchte mir das etwas genauer ansehen und herausfinden, wie wir das verbessert haben.
Auf einem Pixel 6a mit Android 16 dauerte das Einfügen eines einzelnen Start- und Endabschnitts etwa 1.200 Nanosekunden.
Das mag nicht viel erscheinen, aber wenn Sie sehr aktiven Code haben, der viele Trace-Abschnitte hinzufügt, ist das Zeit, die Ihre App sonst für das Rendern von Frames aufwenden könnte.
Wir möchten Ihnen diese Zeit nicht nehmen.
Mit dem AndroidX Perfetto SDK haben wir die Dinge tatsächlich ein wenig verbessert.
Dies war ein experimentelles SDK, das wir für das Composition Tracing eingeführt haben und das die Situation erheblich verbessert hat.
Wir wollten eigentlich nie, dass Sie das veröffentlichen, denn das ist das Perfetto C SDK mit JNI-Bindungen und das C SDK ist etwa 4 MB groß.
Das ist nichts, was wir jemals von dir wollten.
Mit Tracing 2 haben wir jetzt eine speziell entwickelte Bibliothek.
Wir haben den Overhead noch weiter verbessert und Tracing 2 ist jetzt siebenmal schneller. Das bedeutet, dass Sie mehr Zeit für die Dinge haben, die Sie tun möchten, und die Infrastruktur weniger Zeit in Anspruch nimmt.
Was wird dadurch möglich, was vorher nicht möglich war?
Ein Beispiel ist die Laufzeit-Tracing-Funktion von Metro.
Metro ist eine Bibliothek für die Abhängigkeitsinjektion.
Kotlin Multiplatform wird unterstützt.
Es wird ein Kotlin-Compiler-Plug-in verwendet.
Er generiert den gesamten Code für die Abhängigkeitsinjektion und lässt sich in Tracing 2.0 einbinden, um die Kosten für jeden einzelnen @Inject in Ihrem Code leichter zu visualisieren.
Hier ein Beispiel:
Angenommen, wir haben ein App-Diagramm.
In Metro ist alles auf einen Graphen beschränkt, daher gibt es nur ein Feld, AnalyticsTracker.
Wir haben auch eine „provides“-Bindung.
Wir haben einen Netzwerkclient mit OkHttpClient als Implementierung. Die Implementierung von AnalyticsTracker stammt dann aus AnalyticsTrackerImpl. Wir benötigen eine Instanz des Netzwerkclients, die der Graph bereitstellen kann.
Im Hintergrund generiert das Compiler-Plug-in also Code, der so aussieht.
Wenn Sie also AnalyticsTracker haben, erstellt der Getter eine Instanz des Netzwerkclients und übergibt sie an den Konstruktor von AnalyticsTrackerImpl, was gut ist.
Wenn das Tracing aktiviert ist, sehen Sie jetzt nicht nur die Kosten für die Erstellung der Tracker-Implementierung, sondern auch für jede einzelne Abhängigkeit, die sie benötigt.
Jetzt können Sie nicht nur die Kosten für die Instanziierung des Typs sehen, sondern auch alle seine Abhängigkeiten in einem Trace. Das ist wirklich hilfreich.
Anhand dieser Informationen konnte Wise, eine Finanz-App, den App-Start um 45 % verbessern.
Sie konnten Dinge, die beim Start der App nicht initialisiert werden mussten, aufschieben und haben auch einige falsch definierte Elemente identifiziert, bei denen Dinge, die ein Singleton hätten sein sollen, versehentlich keine Singletons waren. Das sind Dinge, die Sie in Ihrem Code sehen und leicht beheben können.
Wenn man sich die Start-up-Messwerte ansieht, sind sie wirklich beeindruckend.
Sie haben nicht nur die Zeit bis zum ersten Frame, sondern auch die Zeit bis zur vollständigen Anzeige verbessert.
Ein anderer Partner, BandLab, verwendet dieselben Informationen zur CI.
Sie kennen also nicht nur die Kosten für jede einzelne @Inject-Bindung, sondern wissen auch, welcher Thread diese Kosten verursacht hat.
Wenn Sie das wissen, können Sie alle teuren @Inject-Bindungen im Hauptthread finden und in Ihrer CI eine Abfrage ausführen, um eine Liste aller teuren Bindungen zu erhalten. Diese können Sie dann der Änderung zuordnen, durch die sie eingeführt wurden, und dann die eigentliche Änderung beheben.
Rahul Ravikumar
Okay.
Bisher habe ich über alle Verbesserungen gegenüber Tracing 1 gesprochen. Jetzt möchte ich auf die neuen Funktionen in Tracing 2 eingehen, die bisher nicht möglich waren.
Eine der Hauptfunktionen ist die Kontextweitergabe. Da moderne Anwendungen immer komplexer werden, werden Arbeitslasten über mehrere Threads, mehrere Koroutinen und mehrere Prozesse hinweg ausgeführt.
Sie möchten visualisieren können, wie eine Anfrage zwischen den einzelnen Threads und Prozessen weitergegeben wird.
Und genau das ermöglicht die Kontextweitergabe.
Wenn ich Ihnen also nur eine Liste von Slices geben würde, ohne Ihnen etwas darüber zu sagen, was was verursacht hat, wären sie zwar nützlich, aber nicht sehr einfach zu verstehen.
Wenn ich Ihnen stattdessen sagen würde, dass dies die kausale Kette von Ereignissen war, wäre es für Sie viel einfacher, herauszufinden, was passiert ist, und dann die Kosten für jeden einzelnen Slice zuzuordnen.
Das ist es, was die Kontextweitergabe ermöglicht.
Die Kontextweitergabe wird vollständig unterstützt. Hier ist ein echtes Beispiel.
Angenommen, Sie sehen sich den Perfetto-Trace com.example.app noch einmal an und stellen fest, dass performInitialize zu lange dauert. Sie möchten die Kosten aufschieben oder auf einen anderen Thread verschieben.
Das ist absolut machbar.
Angenommen, ich habe performInitialize in einen anderen Thread verschoben.
Jetzt ist die hierarchische Struktur des Slices verloren gegangen.
Jetzt können Sie nicht mehr erkennen, dass initializeComponent die Ursache für performInitialize war, es sei denn, Sie sehen sich den Code an.
Im Idealfall würde ein Pfeil anzeigen, dass A die Ursache für B ist, und Sie könnten die kausale Kette von Ereignissen nachvollziehen.
Vielleicht haben Sie sogar einen System-Trace gesehen, in dem wir das für Binder-Transaktionen auf der Plattform tun.
Hier sehen Sie, dass der Compose-Benchmark eine Binder-Transaktion an system_server sendet, um einen Frame vorzubereiten.
Das war also schon immer möglich, wenn man sich die Plattform-Tracing-Funktionen ansieht, aber wir haben die Tools nie für App-Entwickler verfügbar gemacht.
Die Kontextweitergabe von Kotlin-Koroutinen wird jetzt in Tracing 2.0 vollständig unterstützt.
Sie müssen nichts unternehmen.
Sie müssen es nur auspacken.
Hier haben wir eine einzelne Suspend-Methode namens fetchProfile.
Sie ruft eine client.get auf, eine weitere Methode zum Aussetzen, und wechselt dann zu Dispatchers.Default, um die Antwort zu deserialisieren.
Das ist ein sehr typischer Code für Koroutinen, mit dem Sie wahrscheinlich jeden Tag zu tun haben.
Angenommen, wir möchten das nachverfolgen.
Wir fügen einen Trace-Abschnitt hinzu.
Anstelle von „trace“ verwenden wir hier die traceCoroutine API, da es sich um einen aussetzenden Codeblock handelt. Für den inneren Aufruf, bevor wir den Kontext zu Dispatchers.Default wechseln, fügen wir einen weiteren inneren Trace-Abschnitt mit derselben traceCoroutine API hinzu.
In einem Trace sieht das dann in etwa so aus.
Sie sehen, dass die Arbeit in worker-1 begonnen hat und dann ein Kontextwechsel zu worker-3 stattgefunden hat. Im Trace sehen Sie hilfreiche Pfeile, die darauf hinweisen.
Wenn Sie das Quadrat in der Mitte heranzoomen, sehen Sie, dass die Deserialisierung tatsächlich in Worker-1 gestartet wurde und der Aufruf von withContext den Kontextwechsel verursacht hat. Dann wurde sie in Worker-3 fortgesetzt und fetchProfile wurde in Worker-3 fortgesetzt, da die Coroutinen-Maschinerie die Anzahl der Kontextwechsel minimieren möchte.
Sie können genau sehen, wann eine Koroutine angehalten und wann sie fortgesetzt wurde, und Sie können die gesamte Kausalkette der Ereignisse sehen.
Mit Tracing 2 können Sie einem Trace auch benutzerdefinierte Metadaten hinzufügen.
Bisher war das einzige Tool, das Sie hatten, der Name des Trace-Abschnitts. Jetzt können Sie zusätzlich einen Metadatenblock hinzufügen, mit dem Sie beliebig viele Metadateneinträge hinzufügen können.
Und das können Sie dem Trace hinzufügen.
Wenn Sie sich den tatsächlichen Trace ansehen, sehen Sie, dass alle von Ihnen hinzugefügten Metadaten tatsächlich im Trace enthalten sind und von allen Tools genutzt und verwendet werden können.
Jetzt können Sie also alles einbetten, was Sie wissen, und alle zusätzlichen Informationen, die Sie schon immer einbetten wollten.
Ich habe Ihnen eine Reihe von abstrakten Funktionen vorgestellt, die durch Tracing ermöglicht werden. Aber das Beste ist eine neue Funktion in Jetpack Compose namens „Tracing der Neuzusammensetzung“.
Diese Funktion ist in Compose 1.13 enthalten und kann ab sofort verwendet werden.
Hier ein Beispiel:
Das ist das Composition Tracing, mit dem Sie wahrscheinlich alle vertraut sind.
Diese Funktion gibt es schon seit den Anfängen von Compose.
Sie sehen die relativen Kosten einer zusammensetzbaren Funktion und aller ihrer Untersegmente sowie die Kosten der einzelnen Funktionen im Thread, in dem sie aufgetreten sind.
Das ist also ganz einfach.
Was sie Ihnen nicht verrät, ist der Kontext.
Es wird nicht angegeben, warum dies passiert ist.
Sie erfahren nicht, warum die Elemente neu zusammengesetzt wurden.
Wenn ich herauszoome, sehen Sie den Bereich, den wir uns auf der vorherigen Folie angesehen haben.
Links sehen Sie, dass ein Dispatch-Eingabeereignis aufgetreten ist.
Vielleicht hat das einen Status geändert und die Neuzusammensetzung verursacht.
Aber es ist unklar.
Das weißt du nicht sicher.
Mit dem Rekompositions-Tracing fügen wir dem Trace jedoch benutzerdefinierte Metadaten hinzu, die genau angeben, warum Rekompositionen stattgefunden haben.
Außerdem ist es jetzt sehr einfach, zu erkennen, dass diese Ereignisse zusammenhängen.
Wenn Sie darauf klicken, sehen Sie genau, welcher Status sich geändert hat.
Sie erfahren, dass es sich um einen Status handelte, einen änderbaren Statuswert, der von „true“ auf „false“ umgestellt wurde.
Darüber hinaus wird Ihnen der genaue Aufrufstapel angezeigt, der den Fehler verursacht hat.
Hier sehen Sie, dass AbstractClickableNode.performClick aufgetreten ist, was zu einer Änderung des änderbaren Zustands und damit zu einer Neuzusammensetzung geführt hat.
Das ist wirklich eine tolle Sache, die wir bisher nicht machen konnten.
Diese Tools sind auch verfügbar, sorry, alle diese Funktionen sind auch im neuen Android Performance Analyzer-Tool verfügbar, wenn Sie einige der agentischen Tools ausprobieren möchten.
Und rechts.
Ich habe Ihnen bereits gesagt, dass die Kontextweitergabe für Kotlin-Koroutinen vollständig unterstützt wird.
Aber was ist mit Code, der eine Mischung aus Koroutinen und Nicht-Koroutinen verwendet, oder wenn Sie Threads selbst ausführen oder wenn Sie Brownfield-Code haben, der zu alt ist und aus irgendeinem Grund Callbacks verwendet?
Okay.
Die gute Nachricht ist, dass Sie auch hier alle Vorteile der Kontextweitergabe nutzen können.
Nehmen wir an, wir haben eine Brownfield-API, die einen Callback und einen Executor verwendet.
Die Implementierung verwendet executor.execute, ruft runCatching auf (das ist das Implementierungsdetail), erhält die Antwort und informiert den Callback über die Antwort.
Okay.
Wenn ich das jetzt verfolgen möchte, gebe ich Tracer.global.trace ein, füge ein Label hinzu und umschließe den Block.
Da ich aber auch die kausale Kette von Ereignissen beibehalten möchte, akzeptiere ich einen neuen, optionalen Typ, der als Propagierungstoken bezeichnet wird.
Ich übergebe das Token einfach an die innere Trace API.
Wer auch immer diese Methode aufruft, kann mir optional das Propagierungstoken geben, damit ich die kausale Kette der Ereignisse beibehalten kann.
Okay.
Der Aufrufer dieser API kann jetzt eine aussetzende API sein.
Hier habe ich eine „suspend fun“ profileRequest, die jetzt die API aufruft, die einen Callback und einen Executor verwendet.
Da ich hier zwei Welten verbinde, nämlich die Welt der Unterbrechungen mit der Welt der Callbacks, verwende ich den suspendCoroutine-Builder.
Ich erhalte eine Fortsetzung, rufe dann die API auf und setze die Fortsetzung im Callback fort, sobald ich die Antwort erhalte.
Und hier ist der Executor, den ich definiere, namens profileRequestContext.
Es ist ein Single-Thread-Executor.
Und da traceCoroutine ein Propagierungstoken in den Coroutine-Kontext einbettet, kann ich das Propagierungstoken aus dem Coroutine-Kontext abrufen und dann als Eingabe an die Brownfield-API übergeben, um die kausale Kette von Ereignissen beizubehalten.
Im Trace ist das sehr offensichtlich.
Hier haben wir also etwas, das in worker-3 gestartet wurde, nach dem Kontextwechsel zum Single-Thread-Executor gewechselt ist und dann wieder auf worker fortgesetzt wurde.
Das ist also ziemlich praktisch.
Rahul Ravikumar
Okay.
Sie können auch Tracer.global anpassen.
Bisher bietet die Tracing 2.0 API sehr gute Standardwerte für Tracer.global. Sie können diese aber auch anpassen.
Außerdem unterstützt Tracing 2.0 die JVM und Android, da wir möchten, dass Tracing für Java- und Android-Bibliotheken vollständig unterstützt wird.
Die drei Schritte für die Initialisierung sind: Sie erstellen eine Trace-Senke.
Eine Trace-Senke ist im Grunde die Art und Weise, wie Trace-Pakete gespeichert werden.
Sie definiert das Wire-Format und den Speicherort der Ausgabe-Traces.
Wir bieten eine sofort einsatzbereite Implementierung für eine Trace-Senke, die das Perfetto-Format unterstützt. Sie können aber natürlich auch Ihre eigene Trace-Senke schreiben.
Dies geht auf den Versuch zurück, das Problem zu lösen, dass atrace das einzige Backend war, das in Tracing 1 verfügbar war.
Das wollten wir ändern.
Wenn Sie also eine OpenTelemetry-Senke schreiben oder Ihre eigene Senke verwenden möchten, ist das jetzt möglich.
Sie können diese Einstellung überschreiben.
Sobald Sie eine Senke haben, können Sie einen Trace-Treiber erstellen und ihm die Senke zuweisen.
Optional können Sie auch auswählen, welche Kategorien Sie aktivieren möchten.
Hier kommen die Kategorien ins Spiel.
Sie können also festlegen, dass in der Release-Version einige Kategorien wichtiger sind als andere, und diese Kategorien aktivieren.
Sie können auch einfach sagen: „Ich möchte alle Kategorien aktivieren, unabhängig davon, woher sie stammen.“
Das ist völlig in Ordnung.
Sie können auch benutzerdefinierte Attribute hinzufügen, die global für Ihren Trace gelten.
Sie können also Informationen wie die Build-Version, die Variante oder andere Metadaten, die Sie an einen Trace anhängen möchten, über die addAttribute API einbetten.
Schließlich können Sie einen Tracer registrieren, indem Sie setGlobalTracer aufrufen.
Dadurch wird Tracer.global an diese Instanz des Tracers gebunden und ist anschließend für den Prozess verfügbar.
Unter Android haben Prozesse einen viel besser definierten Lebenszyklus. Sie müssen also nur AbstractTraceDriver.Factory implementieren, die Methode create überschreiben und genau das tun, was Sie zuvor getan haben.
Sie erstellen also die Senke und dann den Treiber.
Sie müssen sich hier nicht registrieren.
Wir registrieren Sie einfach, weil wir wissen, dass dies zuerst passiert ist.
Okay.
Ich möchte noch einmal betonen, dass Tracing plattformübergreifend funktioniert.
Das ist eine bewusste Entscheidung, da Compose auf mehreren Plattformen ausgeführt wird und wir Ihnen die Möglichkeit geben möchten, an so vielen Stellen wie möglich Traces zu erstellen.
Derzeit unterstützen wir Android und JVM, aber unsere Implementierung lässt es völlig offen, ob wir in Zukunft weitere Plattformen unterstützen möchten.
Rahul Ravikumar
R8, das Tool, das zum Verkleinern und Optimieren von Quellcode auf Android verwendet wird, nutzt Tracing 2.0.
Durch die Nutzung der Traces haben wir R8 so optimiert, dass Ihr Code um 25 % schneller verkleinert und optimiert wird.
Das bedeutet kürzere Build-Zeiten und kürzere Wartezeiten für Optimierungsdurchläufe.
Ich werde nicht jede einzelne Optimierung in R8 durchgehen, sondern nur eine, um Ihnen einen Eindruck davon zu vermitteln, was mit Tracing möglich ist.
R8 ist im Kern ein Source-to-Source-Transformer.
Sie erhalten also eine Reihe von JARs, die Sie verkleinern und optimieren, und erstellen eine weitere Reihe von JARs, die Sie dexen können.
Das AGP liest zuerst JARs, also nicht nur Ihre App, sondern auch alle ihre Abhängigkeiten.
Es werden also Gradle-Worker verwendet, um alle Eingabe-JARs zu lesen.
Sie haben sehr viele JARs, die Sie lesen möchten.
Nachdem die JAR-Dateien gelesen wurden, beginnt R8 mit dem Parsen des Codes und erstellt ein Modell, das Ihren Code und alle seine Einstiegspunkte darstellt. So kann der Code optimiert und komprimiert werden.
Das ist der Slice „await“, der angibt, wann R8 wirklich bereit ist, den Optimierungsprozess zu starten.
Möglicherweise sehen Sie in der Mitte einen ungenutzten Bereich und fragen sich, warum wir diese ungenutzte CPU-Zeit haben.
Das liegt daran, dass die alte API so funktioniert hat, dass wir gewartet haben, bis alle Worker alle ihre Dateien gelesen haben, bevor wir etwas parsen.
Natürlich können wir das beheben, und genau das haben wir getan.
In AGP 9 werden die Eingabe-JARs sofort nach dem Lesen verarbeitet.
Und wir mussten nicht warten, bis jeder einzelne Worker seine Eingaben gelesen hatte.
Dadurch hat sich die Zeit, die wir auf das Lesen aller Eingaben warten, erheblich verkürzt.
Das ist eine offensichtliche Optimierung, die Sie in einem Trace sofort erkennen, aber nicht so leicht, wenn Sie sich nur den Quellcode ansehen, um herauszufinden, was optimiert werden sollte.
Ein weiteres Tool, das wir als AndroidX-Entwickler täglich verwenden, ist Metalava.
Metalava ist ein Tool, mit dem wir sicherstellen, dass alle von uns bereitgestellten APIs die von Ihnen erwarteten API-Kompatibilitätsgarantien bieten und von externen Personen vollständig genutzt werden können, ohne dass es zu Problemen kommt, die wir vermeiden möchten.
Dieses Tool wird für jede öffentliche und eingeschränkte API ausgeführt. Jede Sekunde, die wir hier sparen, ist also Zeit, die wir als AndroidX-Entwickler zurückgewinnen.
Metalava hat Tracing ebenfalls verwendet, um die Post-Submit-Zeiten um stolze 15 % zu verbessern.
In der Ablaufverfolgung wird erklärt, was passiert ist. Metalava führt zwei Arten von Prüfungen durch: die erste für eingeschränkte APIs und die zweite für öffentliche APIs.
Vielleicht erkennen Sie ein Muster.
Wir richten eine ganze Reihe von Dingen ein, um diese Prüfungen zweimal auszuführen.
Alle Programmeingaben werden gelesen und wir erstellen das PSI (Program Structure Interface), also die genaue Form Ihres Codes, die analysiert werden kann.
Anschließend erstellen wir den AST, bevor wir alle gewünschten Lint-Prüfungen anwenden.
Und wir führen diese aufwendige Arbeit zweimal aus.
Wie haben wir das Problem behoben?
Wir haben es nur einmal gemacht.
Anstatt die gesamte Einrichtung zweimal durchzuführen, haben wir sie nur einmal vorgenommen und die Eingaben sowohl für eingeschränkte als auch für öffentliche Prüfungen wiederverwendet. Dadurch konnten wir alle diese Einsparungen erzielen.
Ja.
Okay.
Rahul Ravikumar
Nachdem ich Ihnen nun einen Vorgeschmack darauf gegeben habe, was mit Tracing 2.0 möglich ist, möchte ich noch darauf hinweisen, dass Tracing zwar wichtig ist, aber auch das Wissen, was getracet werden soll.
Sie sollten Traces nicht überall verteilen.
Sie sollten sehr vorsichtig sein, was Sie auswählen. Ich werde versuchen, Ihnen einige Richtlinien zu geben, was sich lohnt, nachzuverfolgen.
Als Erstes sollten Sie nach unerklärlichen Lücken im Trace suchen.
Wenn Sie also Threads im Status „Ausgeführt“ oder „Ausführbar“ haben und offensichtliche Lücken im System-Trace sehen, die Sie sich nicht erklären können, und Sie nicht wissen, was Ihre App gerade tut, sollten Sie das genauer untersuchen.
An diesem Punkt kann ich einen Methoden-Trace mit dem System-Trace überlagern und sehen, was sich zu tracen lohnt. Dann kann ich diese relevanten Trace-Abschnitte hinzufügen.
Sie sollten auch anhand von User Journeys priorisieren.
Sie sollten sich also den Anwendungsstart ansehen.
Sie möchten sich die wichtigsten User Journeys in Ihrer App ansehen und herausfinden, ob Sie sie anhand eines Traces erklären können.
Wann immer Sie also etwas nicht nachvollziehen können, lohnt es sich, es zu verfolgen.
Sie sollten sich auch tiefe Aufrufstapel oder unerklärliche CPU-Nutzung aus einem Trace ansehen und versuchen, genau herauszufinden, was vor sich geht. Anschließend sollten Sie diese Teile Ihrer Codebasis in Traces konvertieren.
Sie können sie also nachverfolgen.
Und wenn Ihre Threads inaktiv sind, sollten Sie nachvollziehen können, warum das so ist.
Sie sollten auf E/A, eine Binder-Transaktion oder auf externe Eingabeereignisse warten, die Sie erwarten.
Aber wenn Sie unerklärliche Schlafprobleme haben, dann ist das auf jeden Fall eine Untersuchung wert, denn offensichtlich könnte da etwas dahinterstecken.
Nachdem Sie alle Trace-Abschnitte in den Code eingefügt haben, sollten Sie sie in Benchmarks umwandeln, damit Sie Änderungen daran verfolgen können.
Wenn sich Ihre Codebasis weiterentwickelt, können Sie also sehen, ob sich die Messwerte ändern und ob Ihre Dashboards tatsächlich große Änderungen widerspiegeln. Dann wissen Sie, dass sich etwas Wichtiges geändert hat.
Oder Sie überspringen alle vorherigen Schritte und lassen Ihren Agenten die Arbeit erledigen.
Nehmen Sie diese Richtlinien, machen Sie daraus einen Skill, weisen Sie Ihren bevorzugten KI-Agenten darauf hin und lassen Sie ihn loslegen.
Ja.
Rahul Ravikumar
Wenn ich Sie davon überzeugt habe, dass Tracing eine lohnende Sache ist und Sie es tun sollten, finden Sie hier die Projekteinrichtung.
Das ist ganz einfach.
Tracing ist einfach eine herkömmliche Java-Bibliothek. Sie definieren also eine Abhängigkeit von androidx.tracing:tracing-wire, der Perfetto-kompatiblen Kombination aus Trace-Treiber und Tracing. Die aktuelle stabile Version ist 2.0.5.
Wenn Sie eine Java-Bibliothek verwenden, ist es genau dasselbe. Auch hier handelt es sich um eine vollständig plattformübergreifende API.
Wir möchten sie auf andere Plattformen ausweiten, wenn es sinnvoll ist.
Und ja, ich wollte nur eine kurze Zusammenfassung machen.
Ich habe also über all diese neuen coolen Funktionen gesprochen, die wir hinzugefügt haben.
Die Kontextweitergabe ist also wirklich sehr leistungsstark.
Wir können benutzerdefinierte Informationen, Metadaten, Aufrufstapelinformationen und alle möglichen anderen Dinge einbetten.
Ich habe über viele, viele Dinge nicht gesprochen.
Sie können das Tracing-Format ändern, die Trace-Senke ändern und die Tracing-Daten in OpenTelemetry übertragen.
Das sind alles Dinge, die Sie tun können.
Tracing ist plattformübergreifend.
Auch das ist eine sehr bewusste Entscheidung, denn wir möchten, dass möglichst viele Menschen diese Funktion nutzen können.
Und alle von uns bereitgestellten Tools, ob Perfetto, AndroidX Benchmarks, der neue Performance Analyzer oder der alte Android Profiler, unterstützen Tracing 2.0.
Vielen Dank fürs Zuhören.
Ich wünsche Ihnen einen tollen Dev Summit und danke Ihnen.