11 März 2025 (updated: 11 März 2025)

Verbessern Sie die Leistung Ihrer App mit Android Profiler

Chapters

      Haben Sie jemals einen plötzlichen Freeze einer App bemerkt, die Sie entwickelt haben? Stellen Sie sich vor, es ist gerade passiert. Ist Ihr nächster Schritt, den App-Prozess mit Android Profiler zu profilieren? Wenn nicht, habe ich diesen Artikel speziell für Sie geschrieben!

      Android Profiler ist eine Sammlung von Tools, die seit Android Studio 3.0 verfügbar sind und die vorherigen Android Monitor-Tools ersetzen. Die neue Suite ist viel fortschrittlicher bei der Diagnose von Leistungsproblemen von Apps. Sie kommt mit einer gemeinsamen Zeitachse und detaillierten CPU-, Speicher- und Netzwerk-Profilern. Durch geschickte Nutzung können wir viel Zeit sparen, die sonst mit Debugging oder dem Scrollen durch Protokolle im Logcat-Fenster verloren geht.

      Um auf die Profiling-Tools zuzugreifen, klicken Sie auf View > Tool Windows > Android Profiler oder finden Sie ein entsprechendes Tool-Fenster in der Symbolleiste. Um Echtzeitdaten zu sehen, müssen Sie ein Gerät mit aktiviertem USB-Debugging anschließen oder den Android-Emulator verwenden und den App-Prozess auswählen. Ich empfehle Ihnen, das offizielle Android-Benutzerhandbuch zu lesen, um zu lernen, wie Sie alle in diesem Fenster angezeigten Daten inspizieren können.

      Lernen Sie gerne anhand von Beispielen? Ich habe zwei Beispiele vorbereitet, um mit dem CPU Profiler zu üben. Dies sind kleine Apps, die auf einige Leistungsprobleme gestoßen sind. Lassen Sie uns versuchen, diese zu lösen!

      Optimierung der Leistung von Android-Apps

      Beispiel 1

      Wir beginnen mit der Entwicklung einer einfachen Android-Anwendung, die eine Liste von aufeinanderfolgenden Daten anzeigt (die noch nicht passiert sind). Unter jedem Datum können wir die verbleibende Zeit in Tagen, Stunden, Minuten und Sekunden anzeigen.

      Der Code beider Beispiele ist auf GitHub verfügbar, sodass Sie das Repository einfach klonen und das Projekt in Android Studio öffnen können. Überprüfen Sie vorerst die Revision mit dem Tag sample-1-before.

      Beginnen Sie mit der Definition eines Layouts, das aus einem RecyclerView besteht, das innerhalb von SwipeRefreshLayout platziert ist. Dies ermöglicht es, die Daten bei einer vertikalen Wischgeste zu aktualisieren.

      Erstellen Sie als Nächstes eine Activity, die unser Layout aufbläst, die Benutzerinteraktion verarbeitet und Operationen im Haupt-Thread ausführt, um aktualisierte Daten anzuzeigen:

      In Zeile 9 verwenden wir den RecyclerView-Adapter. Wir verwenden die Recycler-Bibliothek von android-commons (die in den meisten EL Passion Android-Projekten verwendet wird). Eine generische Funktion nimmt eine Liste von Elementen, einen Verweis auf die Layout-Ressource des Elements und einen Binder. Ziel ist es, den Code prägnant zu halten und den RecyclerView-Adapter ohne Boilerplate-Code einzurichten.

      Am Ende der onCreate-Funktion setzen wir den Listener, um über Aktualisierungsaktionen, die durch SwipeRefreshLayout ausgelöst werden, informiert zu werden. Die referenzierte refreshData-Funktion ersetzt die Liste durch frische neue Elemente und benachrichtigt den Adapter über Änderungen der Daten.

      In Zeile 25 generieren wir eine Liste von 1000 Elementen. Jedes Element setzt seine Eigenschaften in Bezug auf das aktuelle Datum und die Zeitverschiebung in Tagen. Die Zeitverschiebung reicht von 0 bis 999 und beeinflusst das Datum, das durch das Element angezeigt wird (siehe Zeile 29). Wir verwenden ThreeTenABP als unsere API für Daten und Zeiträume. Es ist ein unschätzbarer Backport des java.time.*-Pakets, das von Jake Wharton für Android optimiert wurde.

      In Zeile 36 führen wir einige Operationen durch, um die verbleibende Zeit als eine menschenlesbarere Dauer zu erhalten.

      In Zeile 50 binden wir ein Element mit dem ViewHolder, um itemView an einer bestimmten Position zu aktualisieren. Wir greifen auf Ressourcen zu, um den String mit dem Wert remainingTime zu formatieren.

      Das Element selbst hält die Werte formattedDate und remainingTime, die bereit sind, in den entsprechenden TextView-Komponenten angezeigt zu werden. Lassen Sie uns das folgende Elementlayout verwenden:

      Starten Sie die App und wischen Sie, um die Daten zu aktualisieren. Haben Sie ein Einfrieren bemerkt? Wahrscheinlich nicht. Das hängt stark von der CPU Ihres Geräts und anderen Prozessen ab, die CPU-Zeit verbrauchen. Starten Sie jetzt das Android Profiler Tool-Fenster und wählen Sie die entsprechende Zeitleiste aus, um den CPU Profiler zu öffnen. Verbinden Sie Ihr Gerät und wischen Sie erneut, um die Daten zu aktualisieren. Beachten Sie, dass Profiler-Threads zum Anwendungsprozess hinzugefügt werden und zusätzliche CPU-Zeit verbrauchen. Ich nehme an, dass Sie jetzt bereits Frame-Skips erlebt haben. Schauen Sie sich das Logcat an, da der Choreograf Sie bereits über eine hohe Verarbeitung warnen sollte:

      I/Choreographer: Skipped 147 frames! Die Anwendung könnte zu viel Arbeit in ihrem Haupt-Thread verrichten.

      Cool! Wir können mit unserer Inspektion beginnen. Schauen Sie sich die CPU Profiler-Zeitleiste an:

      CPU Profiler timeline

      Über dem Diagramm gibt es eine Ansicht, die die Benutzerinteraktion mit der App darstellt. Alle Benutzereingaben werden hier als lila Kreise angezeigt. Sie können einen Kreis sehen, der den Wischvorgang darstellt, den wir durchgeführt haben, um die Daten zu aktualisieren. Etwas weiter unten finden Sie die derzeit angezeigte Sample1Activity. Dieser Bereich wird als Ereigniszeitleiste bezeichnet.

      Unter den Ereignissen befindet sich die CPU-Zeitleiste, die grafisch die CPU-Nutzung der App und anderer Prozesse im Verhältnis zur insgesamt verfügbaren CPU-Zeit anzeigt. Darüber hinaus können Sie die Anzahl der Threads beobachten, die Ihre App verwendet.

      Unten sehen Sie die Thread-Aktivitätszeitleiste, die zum Anwendungsprozess gehört. Jeder Thread befindet sich in einem von drei Zuständen, die durch Farben angezeigt werden: aktiv (grün), wartend (gelb) oder schlafend (grau). Ganz oben in der Liste finden Sie den Haupt-Thread der App. Auf meinem Gerät (Nexus 5X) verwendet er ~35% der CPU-Zeit für etwa 5 Sekunden. Das ist viel! Wir können eine Methodenverfolgung aufzeichnen, um zu sehen, was dort passiert.

      Klicken Sie auf die Aufnahmetaste 🔴, kurz bevor Sie wischen, um die Aktion zu aktualisieren, und stoppen Sie die Aufnahme , kurz nachdem die Datenaktualisierung abgeschlossen ist. Wenn Sie fertig sind, beachten Sie, dass der Methodenverfolgungsbereich gerade erschienen ist:

      android profiler recording

      Wir beginnen unsere Analyse mit dem Call Chart, das im ersten Tab angezeigt wird. Die horizontale Achse stellt den Zeitverlauf dar. Auf der vertikalen Achse werden die Aufrufer und deren Aufgerufene (von oben nach unten) angezeigt. Methodenaufrufe werden auch farblich unterschieden, je nachdem, ob es sich um einen Aufruf an die System-API, eine Drittanbieter-API oder unsere Methode handelt. Beachten Sie, dass die Gesamtzeit für jeden Methodenaufruf die Summe der Selbstzeit der Methode und der Zeit ihrer Aufgerufenen ist. Aus diesem Diagramm können Sie ableiten, dass das Leistungsproblem irgendwo innerhalb der generateItems-Methode liegt. Bewegen Sie die Maus über die Leiste, um weitere Details zur verstrichenen Zeit zu überprüfen. Sie können auch doppelt auf die Leiste klicken, um die Methodendeklaration im Code zu sehen. Es ist ziemlich schwierig, mehr aus diesem Tab abzuleiten, da es viel Zoomen und Scrollen erfordert, also wechseln wir zum nächsten Tab.

      android profiler

      Das Flame Chart ist viel besser geeignet, um zu zeigen, welche Methoden unserer wertvollen CPU-Zeit in Anspruch genommen haben. Es aggregiert dieselben Aufrufstapel und kehrt das Diagramm aus dem vorherigen Tab um. Anstelle vieler kurzer horizontaler Balken wird ein einzelner längerer Balken angezeigt. Schauen Sie sich das jetzt an:

      android profiler CPU time

      Zwei verdächtige Methoden gefunden. Würden Sie glauben, dass getRemainingTime die gesamte Ausführungszeit der Methode über 2 Sekunden und LocalDateTime.format über 1 Sekunde CPU-Zeit in Anspruch nehmen wird?

      5

      Beachten Sie, dass diese Zeit auch jeden Zeitraum umfasst, in dem der Thread nicht aktiv war. In der oberen rechten Ecke des Methodenverfolgungsbereichs können Sie die Zeitinformationen so umschalten, dass sie in der Thread-Zeit angezeigt werden. Wenn wir einen einzelnen Thread analysieren, könnte dies die bevorzugte Option sein, da sie den CPU-Zeitverbrauch zeigt, der nicht von anderen Threads beeinflusst wird.

      Okay, lassen Sie uns weitermachen. Öffnen Sie jetzt den letzten Tab, um das Bottom Up-Diagramm zu sehen. Es zeigt eine Liste von Methodenaufrufen, die nach dem CPU-Zeitverbrauch absteigend sortiert sind. Dieses Diagramm gibt uns detaillierte Zeitinformationen (in Mikrosekunden). Durch das Erweitern der Methoden können Sie deren Aufrufer finden.

      6

      Holen Sie sich aus dem Diagramm Zeitinformationen über die Methoden, die wir beschuldigt haben, zu viel CPU-Zeit zu verbrauchen. Setzen Sie sie in Beziehung zu zwei Methoden aus ihrem Aufrufstapel:

      Sie können sehen, dass getRemainingTime und LocalDateTime.format über 80% der aufgezeichneten Methodenverfolgung verbrauchen! Um dieses Einfrieren zu beheben, müssen wir an der Generierung der Elemente arbeiten. Das ist offensichtlich.

      Was ist also zu tun? Sie haben wahrscheinlich bereits mehrere Lösungen gefunden. Wir führen eine rechenintensive Berechnung durch, um 1000 Elemente zu erstellen (keine kleine Zahl). Sie können darüber nachdenken, eine Paginierung zu implementieren, um die Daten schrittweise zu erstellen und anzuzeigen. Das ist eine großartige Idee, da sie skalierbar ist. Dieses Mal möchte ich jedoch einen anderen Weg einschlagen. Was wäre, wenn wir alle Formatierungen kurz vor der Anzeige der Daten im RecyclerView an der angegebenen Position durchführen — wenn wir Item mit RecyclerView.ViewHolder binden? Dadurch würden wir die Methoden getRemainingTime und LocalDateTime.format nur für die wenigen aktuell angezeigten und bereit zu zeigenden Elemente aufrufen — nicht tausendmal wie zuvor. Um dies zu erreichen, müssen wir die Eigenschaften von Item aktualisieren, um nur die notwendigen Daten zu halten, um die Formatierung später durchzuführen:

      Das erfordert folgende Änderungen in den Funktionen generateItems und bindItem:

      Lassen Sie uns sehen, dass wir die Funktion createItem inline gesetzt haben, da jetzt alle Formatierungen innerhalb der Methode bindItem stattfinden. Überprüfen Sie die Revision mit dem Tag sample-1-after, um diese Änderungen zu erhalten.

      Es ist Zeit, den CPU Profiler erneut zu starten und die Methodenverfolgung aufzuzeichnen, nachdem Änderungen in unserem Code vorgenommen wurden. Schauen Sie sich das Call Chart an, um zu überprüfen, ob unsere Optimierung gut verlaufen ist:

      7 Android Profiler Call Chart

      Wenn Sie die Maus über die Funktion generateItems bewegen, werden Sie feststellen, dass sie jetzt ~0,3 Sekunden der Wand-Uhrzeit verbraucht. Das sind über 13 Mal weniger CPU-Zeit als vor der Optimierung! Bevor wir mit dem Feiern beginnen, wechseln wir zum Flame Chart, um sicherzustellen, dass unsere Änderungen keine negativen Auswirkungen auf die Gesamtdauer der Methode bindItem haben. Glücklicherweise verbraucht sie bis zu 0,1 Sekunden.

      8Zusätzlich können Sie es scrollen, um sicherzustellen, dass unsere Optimierung die Gesamtleistung der App nicht beeinträchtigt. Versuchen Sie, während eines solchen Scrollens die Methodenverfolgung aufzuzeichnen. Beachten Sie, dass der Choreograf nicht mehr über das Überspringen von Frames klagt. Erfolg! Der Code ist optimiert und wir sind mit dem ersten Beispiel fertig!

      Beispiel 2

      Im nächsten Beispiel werden wir größtenteils den Code aus Beispiel 1 nach der Optimierung wiederverwenden. Die einzige Änderung, die wir im Aktivitätslayout vornehmen werden, ist das Hinzufügen eines ImageView über dem RecyclerView. Um den gesamten Inhalt scrollbar zu machen, legen Sie beide Ansichten in ein NestedScrollView:

      Um Konflikte im Scrollverhalten des RecyclerView zu vermeiden, müssen wir das Attribut nestedScrollingEnabled auf false setzen. Überprüfen Sie die Revision mit dem Tag sample-2-before, um dieses Beispiel schnell zu ziehen. Starten Sie die App und wischen Sie, um die Daten zu aktualisieren. Sie sollten ein Einfrieren bemerken, selbst ohne den Android Profiler angeschlossen.

      Dieses Mal habe ich beschlossen, Ihnen die Diagnose selbst zu überlassen, um Ihren Spaß nicht zu verderben. Nach erfolgreicher Optimierung der App-Leistung sollten Sie kein Einfrieren wie im Beispiel 1 erleben. Es gibt nur eine Regel — der Bildschirm, der dem Benutzer angezeigt wird, darf sein Aussehen nicht ändern. Viel Glück!

      Zusammenfassung

      Ich glaube, ich habe dich ermutigt, den Android Profiler häufiger zu nutzen. Ich denke, das ist eine gute Praxis, wenn uns eine reibungslose Benutzererfahrung am Herzen liegt. In diesem Artikel habe ich mich hauptsächlich auf den CPU Profiler konzentriert. Allerdings sind auch der Memory Profiler und der Network Profiler, die im Text nicht behandelt werden, einen Blick wert. Die Aufzeichnung der Speicherzuweisung hilft sehr dabei, Lecks zu finden, z.B. indem man dir vorwirft, dass Bitmaps nicht recycelt wurden. Wie auch immer, das Profiling der Netzwerkaktivität kann zu mehreren Optimierungen führen, die darauf abzielen, den Batterieverbrauch zu reduzieren.

       

      Paweł Gajda

      Senior Android Developer

      Vielleicht ist das Anfang einer wunderbaren Freundschaft?

      Wir sind für neue Projekte verfügbar.

      Contact us