Fallstudie: So konnte das Wear OS-Team von Gmail sein App-Start-up um 50 % verbessern

Der App-Start stellt den ersten Eindruck Ihrer App bei Nutzern dar. Da die Nutzer nicht warten möchten, solltest du darauf achten, dass deine App schnell startet. Um Ihnen zu zeigen, wie ein echtes App-Entwicklungsteam Probleme beim Start seiner App gefunden und diagnostiziert hat, hat das Gmail Wear OS-Team wie folgt vorgegangen.

Das Gmail Wear OS-Team führte eine Optimierung durch, mit besonderem Augenmerk auf die App-Start- und Laufzeit-Rendering-Leistung, um die Kriterien des Teams für die App-Leistung zu erfüllen. Aber selbst wenn Sie keine spezifischen Grenzwerte für das Targeting haben, können Sie den App-Start fast immer verbessern, wenn Sie sich die Zeit nehmen, dies zu untersuchen.

Trace erfassen und App-Start ansehen

Erfassen Sie einen Trace, der den App-Start zur genaueren Prüfung in Perfetto oder Android Studio enthält, um mit der Analyse zu beginnen. In dieser Fallstudie wird Perfetto verwendet, weil es zeigt, was im Gerätesystem außerhalb Ihrer Anwendung passiert. Wenn Sie den Trace in Perfetto hochladen, sieht es so aus:

Abbildung 1: Anfangsansicht des Trace in Perfetto.

Da der Schwerpunkt auf der Verbesserung des App-Starts liegt, suchen Sie die Zeile mit dem benutzerdefinierten Messwert Android-App-Starts. Es ist hilfreich, ihn oben in der Ansicht anzupinnen, indem Sie auf das Stecknadelsymbol klicken, das erscheint, wenn Sie den Mauszeiger auf die Zeile bewegen. Der Balken oder Slice, den Sie in der Zeile Android-App-Starts sehen, gibt den Zeitraum an, den der App-Start abdeckt, bis der erste App-Frame auf dem Bildschirm zu sehen ist. Suchen Sie daher dort nach Problemen oder Engpässen.

Zeile „Android-App-Starts“ mit hervorgehobener Option zum Anpinnen.
Abbildung 2: Pinne den benutzerdefinierten Messwert „Android-App-Starts“ oben im Dashboard an, um die Analyse zu vereinfachen.

Der Messwert Android-App-Starts entspricht der Zeit bis zur ersten Anzeige, auch wenn Sie reportFullyDrawn() verwenden. Wenn Sie die Zeit bis zur vollständigen Anzeige ermitteln möchten, suchen Sie im Perfetto-Suchfeld nach reportFullyDrawn().

Hauptthread prüfen

Untersuchen Sie zuerst, was im Hauptthread passiert. Der Hauptthread ist sehr wichtig, da dort normalerweise das gesamte UI-Rendering stattfindet. Wenn er blockiert ist, kann nichts gezeichnet werden und Ihre Anwendung scheint eingefroren zu sein. Daher sollten Sie dafür sorgen, dass im Hauptthread keine lang andauernden Vorgänge ausgeführt werden.

Suchen Sie die Zeile mit dem Paketnamen Ihrer App und maximieren Sie ihn, um den Hauptthread zu finden. Die beiden Zeilen mit demselben Namen wie das Paket (normalerweise die ersten beiden Zeilen im Abschnitt) stellen den Hauptthread dar. Von den beiden Hauptthreadzeilen repräsentiert die erste den CPU-Status und die zweite Zeile Tracepoints. Pinne die beiden Hauptthreadzeilen unter dem Messwert Android-App-Starts an.

Zeilen für Starts von Android-Apps und Hauptthreads angepinnt.
Abbildung 3: Pinne die Zeilen des Hauptthreads unter dem benutzerdefinierten Messwert „Android-App-Starts“ an, um die Analyse zu unterstützen.

Im „runnable“-Zustand verbrachte Zeit und CPU-Konflikt

Für eine aggregierte Ansicht der CPU-Aktivität während des Anwendungsstarts ziehen Sie den Cursor über den Hauptthread, um die Startzeit der Anwendung zu erfassen. Der Bereich Thread-Status wird eingeblendet. Hier sehen Sie die Gesamtzeit, die innerhalb des ausgewählten Zeitraums in jedem CPU-Status verbracht wurde.

Sieh dir die Zeit an, die im Status Runnable verbracht wurde. Wenn ein Thread den Status Runnable hat, kann er zwar ausgeführt werden, es werden aber keine Aufgaben geplant. Dies könnte darauf hinweisen, dass das Gerät stark ausgelastet ist und keine Aufgaben mit hoher Priorität planen kann. Die oberste, für Nutzer sichtbare Anwendung hat bei der Planung die höchste Priorität. Daher weist ein inaktiver Hauptthread häufig darauf hin, dass intensive Prozesse innerhalb der Anwendung, z. B. das Rendering von Animationen, mit dem Hauptthread um die CPU-Zeit konkurrieren.

Der Hauptthread ist im Bereich „Threadstatus“ mit der Gesamtzeit in verschiedenen Zuständen markiert.
Abbildung 4: Bewerten Sie die relative Zeit in den Status Runnable bis Running, um einen ersten Eindruck davon zu erhalten, wie groß der CPU-Konflikt besteht.

Je höher das Verhältnis der Zeit im Status Runnable zur Zeit im Status Running ist, desto wahrscheinlicher treten CPU-Konflikte auf. Wenn Sie Leistungsprobleme auf diese Weise untersuchen, sollten Sie sich zuerst auf den Frame mit der längsten Ausführung konzentrieren und auf kleinere konzentrieren.

Bei der Analyse der Zeit mit dem Status „Runnable“ solltest du die Gerätehardware berücksichtigen. Da die dargestellte App auf einem Wearable-Gerät mit zwei CPUs ausgeführt wird, wird im Status Runnable mehr Zeit benötigt und es besteht mehr CPU-Konflikte mit anderen Prozessen als bei einem Gerät mit mehr CPUs. Obwohl für eine typische Telefon-App mehr Zeit im Status Runnable verbracht wird als erwartet, kann er im Kontext von Wearables verständlich sein.

Dauer: OpenDexFilesFromOat*

Prüfen Sie jetzt die in OpenDexFilesFromOat* verbrachte Zeit. Im Trace geschieht dies zur selben Zeit wie das bindApplication-Slice. Dieses Segment gibt die Zeit an, die zum Lesen der DEX-Dateien der Anwendung benötigt wird.

Blockierte Binder-Transaktionen

Prüfen Sie als Nächstes die Binder-Transaktionen. Binder-Transaktionen stellen Aufrufe zwischen dem Client und dem Server dar: In diesem Fall ruft die App (der Client) das Android-System (Server) mit einer binder transaction auf und der Server antwortet mit einer binder reply. Achten Sie darauf, dass die Anwendung während des Starts keine unnötigen Bindertransaktionen ausführt, da diese das Risiko von CPU-Konflikten erhöhen. Wenn möglich, verschieben Sie Arbeiten, die Binderaufrufe beinhalten, nach der Startphase der Anwendung. Wenn Sie Binder-Transaktionen ausführen müssen, achten Sie darauf, dass diese nicht länger als die VSync-Aktualisierungsrate Ihres Geräts dauern.

Die erste Binder-Transaktion, die normalerweise zur selben Zeit wie das ActivityThreadMain-Slice erfolgt, scheint in diesem Fall recht lang zu sein. Führen Sie die folgenden Schritte aus, um mehr darüber zu erfahren, was möglicherweise passiert:

  1. Klicken Sie auf das gewünschte Segment der Binder-Transaktion, um die zugehörige Binder-Antwort zu sehen und mehr über die Priorisierung der Binder-Transaktion zu erfahren.
  2. Um die binäre Antwort zu sehen, gehen Sie zum Feld Aktuelle Auswahl und klicken Sie im Abschnitt Folgende Threads auf Binder-Antwort. Im Feld Thread erfahren Sie auch, in welchem Thread die Binder-Antwort erfolgt, wenn Sie manuell dorthin navigieren möchten. Es befindet sich in einem anderen Prozess. Es wird eine Zeile angezeigt, die die Binder-Transaktion und die Antwort verbindet.

    Eine Leitung verbindet den Binder-Aufruf und die Antwort.
    Abbildung 5: Identifizieren Sie die Bindertransaktionen, die beim Start der Anwendung stattfinden, und prüfen Sie, ob Sie sie aufschieben können.
  3. Wenn Sie sehen möchten, wie der Systemserver diese Bindertransaktion verarbeitet, pinnen Sie die Threads Cpu 0 und Cpu 1 oben in Ihrem Bildschirm an.

  4. Ermitteln Sie die Systemprozesse, die die Binder-Antwort verarbeiten, indem Sie die Segmente finden, die den Namen des Binder-Antwort-Threads enthalten, in diesem Fall "Binder:687_11 [2542]". Klicken Sie auf die entsprechenden Systemprozesse, um weitere Informationen zur Bindertransaktion zu erhalten.

Sehen Sie sich diesen Systemprozess an, der mit der Binder-Transaktion von Interesse verknüpft ist, die auf CPU 0 stattfindet:

Systemprozess mit dem Endstatus „Ausführbar (vorausgesetzt)“.
Abbildung 6: Der Systemprozess hat den Status Runnable (Preempted), was darauf hinweist, dass er verzögert wird.

Im Endstatus wird Runnable (Preempted) angezeigt. Das bedeutet, dass sich der Prozess verzögert, weil die CPU etwas anderes tut. Maximieren Sie die Zeilen Ftrace-Ereignisse, um zu sehen, was vorzeitig beendet wird. Scrollen Sie auf dem Tab Ftrace-Ereignisse, der verfügbar wird, durch und suchen Sie nach Ereignissen im Zusammenhang mit dem relevanten Binder-Thread „Binder:687_11 [2542]“. Ungefähr zu der Zeit, zu der der Systemprozess vorzeitig beendet wird, sind zwei Systemserverereignisse aufgetreten, die das Argument „decon“ enthalten und sich somit auf den Display-Controller beziehen. Das klingt vernünftig, weil der Display-Controller die Frames auf dem Bildschirm platziert – eine wichtige Aufgabe. Dann lassen Sie die Ereignisse so, wie sie sind.

FTrace-Ereignisse, die mit der relevanten Binder-Transaktion verknüpft sind, sind hervorgehoben.
Abbildung 7: Die FTrace-Ereignisse zeigen an, dass die Binder-Transaktion durch Display-Controller-Ereignisse verzögert wird.

JIT-Aktivität

Wenn Sie die JIT-Kompilierung (Just-in-Time Compilation) untersuchen möchten, erweitern Sie die zu Ihrer Anwendung gehörenden Prozesse, suchen Sie die beiden Zeilen mit dem Jit-Thread-Pool und pinnen Sie sie oben an Ihrer Ansicht an. Da diese Anwendung beim Start der Anwendung von Baseline Profiles profitiert, erfolgt bis zum Zeichnen des ersten Frames sehr wenig JIT-Aktivität. Dies wird durch das Ende des ersten Choreographer.doFrame-Aufrufs dargestellt. Beachten Sie jedoch den Grund für einen langsamen Start JIT compiling void, der darauf hindeutet, dass die Systemaktivität während des Tracepoint mit dem Label Application creation viele Hintergrund-JIT-Aktivitäten verursacht. Fügen Sie zum Beheben dieses Problems die Ereignisse, die kurz nach dem Erstellen des ersten Frames auftreten, dem Baseline-Profil hinzu. Erweitern Sie dazu die Profilsammlung so, dass die Anwendung verwendet werden kann. In vielen Fällen können Sie dazu eine Zeile am Ende der Makro-Benchmark-Sammlung für das Referenzprofil einfügen. Diese wartet darauf, dass ein bestimmtes UI-Widget auf dem Bildschirm erscheint und anzeigt, dass der Bildschirm vollständig ausgefüllt ist.

Jit-Thread-Pools mit hervorgehobenem Segment „Jit compiling void“.
Abbildung 8. Wenn Sie viele JIT-Aktivitäten sehen, erweitern Sie Ihr Baseline-Profil bis zu dem Punkt, an dem die Anwendung einsatzbereit ist.

Ergebnis

Als Ergebnis dieser Analyse hat das Wear OS-Team von Gmail folgende Verbesserungen vorgenommen:

  • Da bei der Analyse der CPU-Aktivität beim Start der Anwendung Konflikte erkannt wurden, wurde das rotierende Ladesymbol ersetzt, mit dem angezeigt wurde, dass die Anwendung mit einem einzelnen statischen Bild geladen wird. Außerdem wurde der Ladebildschirm so verlängert, dass der Schimmerstatus, der zweite Bildschirmstatus, der angibt, dass die App geladen wird, zurückgestellt wurde, um CPU-Ressourcen freizugeben. Dadurch wurde die Latenz beim Starten der App um 50 % verbessert.
  • Anhand der Zeit, die mit OpenDexFilesFromOat* und JIT-Aktivitäten verbracht wurde, konnten in R8 Baseline-Profile umgeschrieben werden. Dadurch wurde die Latenz beim Starten der Anwendung um 20 % verbessert.

Hier einige Tipps des Teams zur effizienten Analyse der App-Leistung:

  • Richten Sie einen laufenden Prozess ein, der Traces und Ergebnisse automatisch erfasst. Richten Sie gegebenenfalls automatisiertes Tracing für Ihre Anwendung mithilfe von Benchmarking ein.
  • Führen Sie A/B-Tests für Änderungen durch, die Ihrer Meinung nach die Leistung verbessern, und lehnen Sie sie ab, wenn sie dies nicht tun. Sie können die Leistung in verschiedenen Szenarien mithilfe der MacroBenchmark-Bibliothek messen.

Weitere Informationen finden Sie in den folgenden Ressourcen: