Beim Rendern der Benutzeroberfläche wird ein Frame aus Ihrer App generiert und auf dem Bildschirm angezeigt. Damit die Interaktion eines Nutzers mit Ihrer App reibungslos verläuft, muss Ihre App Frames in weniger als 16 ms rendern, um 60 fps zu erreichen. Weitere Informationen Wenn du 90 fps erreichen möchtest, sinkt dieses Zeitfenster auf 11 ms und bei 120 fps auf 8 ms.
Wenn Sie dieses Zeitfenster um 1 ms überschreiten, wird der Frame nicht 1 ms zu spät angezeigt, sondern von Choreographer verworfen. Wenn die Benutzeroberfläche Ihrer App langsam gerendert wird, muss das System Frames überspringen und der Nutzer nimmt Ruckeln in Ihrer App wahr. Dieses Phänomen wird als Jank bezeichnet. Auf dieser Seite wird beschrieben, wie Sie Ruckeln diagnostizieren und beheben.
Wenn Sie Spiele entwickeln, in denen das View-System nicht verwendet wird, umgehen Sie Choreographer. In diesem Fall hilft die Frame Pacing Library dabei, dass OpenGL- und Vulkan-Spiele auf Android reibungslos gerendert werden und die Framerate korrekt ist.
Verzögerungen erkennen
Es kann schwierig sein, den Code in Ihrer App zu finden, der Ruckeln verursacht. In diesem Abschnitt werden drei Methoden zum Erkennen von Rucklern beschrieben:
Mit der visuellen Prüfung können Sie alle Anwendungsfälle in Ihrer App in wenigen Minuten durchgehen. Sie liefert jedoch nicht so viele Details wie Systrace. Systrace bietet mehr Details, aber wenn Sie Systrace für alle Anwendungsfälle in Ihrer App ausführen, können Sie mit so vielen Daten überflutet werden, dass die Analyse schwierig wird. Sowohl die visuelle Überprüfung als auch Systrace erkennen Ruckeln auf Ihrem lokalen Gerät. Wenn Sie Verzögerungen auf lokalen Geräten nicht reproduzieren können, können Sie benutzerdefiniertes Leistungsmonitoring erstellen, um bestimmte Teile Ihrer App auf Geräten zu messen, die im Feld ausgeführt werden.
Visuelle Prüfung
Mithilfe der visuellen Inspektion können Sie die Anwendungsfälle ermitteln, die zu Rucklern führen. Öffnen Sie Ihre App, gehen Sie die verschiedenen Bereiche manuell durch und suchen Sie nach Rucklern in der Benutzeroberfläche.
Hier sind einige Tipps für die Durchführung visueller Prüfungen:
- Führen Sie eine Release- oder zumindest eine nicht debuggbare Version Ihrer App aus. Die ART-Laufzeit deaktiviert mehrere wichtige Optimierungen, um Debugging-Funktionen zu unterstützen. Achten Sie also darauf, dass Sie etwas Ähnliches sehen wie ein Nutzer.
- GPU-Rendering für Profil aktivieren Bei „GPU-Rendering für Profil“ werden Balken auf dem Bildschirm angezeigt, die visuell darstellen, wie viel Zeit zum Rendern der Frames eines UI-Fensters im Vergleich zum Benchmark von 16 ms pro Frame benötigt wird. Jeder Balken hat farbige Komponenten, die einer Phase in der Rendering-Pipeline entsprechen. So können Sie sehen, welcher Teil am längsten dauert. Wenn der Frame beispielsweise viel Zeit mit der Verarbeitung von Eingaben verbringt, sollten Sie den App-Code prüfen, der Nutzereingaben verarbeitet.
- Sehen Sie sich Komponenten an, die häufige Ursachen für Verzögerung sind, z. B.
RecyclerView. - Starten Sie die App über einen Kaltstart.
- Führen Sie Ihre App auf einem langsameren Gerät aus, um das Problem zu verschlimmern.
Wenn Sie Anwendungsfälle finden, die Verzögerungen verursachen, wissen Sie möglicherweise bereits, was die Verzögerungen in Ihrer App verursacht. Wenn Sie weitere Informationen benötigen, können Sie Systrace verwenden, um die Ursache genauer zu untersuchen.
Systrace
Systrace ist zwar ein Tool, das zeigt, was das gesamte Gerät macht, kann aber auch nützlich sein, um Ruckeln in Ihrer App zu erkennen. Systrace hat einen minimalen System-Overhead, sodass Sie während der Instrumentierung realistisches Ruckeln beobachten können.
Erstellen Sie mit Systrace einen Trace, während Sie den ruckeligen Anwendungsfall auf Ihrem Gerät ausführen. Eine Anleitung zur Verwendung von Systrace finden Sie unter System-Trace in der Befehlszeile aufzeichnen. Systrace ist nach Prozessen und Threads unterteilt. Suchen Sie in Systrace nach dem Prozess Ihrer App. Er sieht in etwa so aus wie in Abbildung 1.
Das Systrace-Beispiel in Abbildung 1 enthält die folgenden Informationen zum Identifizieren von Rucklern:
- Systrace zeigt an, wann jeder Frame gezeichnet wird, und kennzeichnet jeden Frame farblich, um langsame Rendering-Zeiten hervorzuheben. So lassen sich einzelne fehlerhafte Frames genauer finden als bei einer visuellen Prüfung. Weitere Informationen finden Sie unter UI-Frames und ‑Benachrichtigungen prüfen.
- Systrace erkennt Probleme in Ihrer App und zeigt Warnungen sowohl in einzelnen Frames als auch im Bereich Warnungen an. Am besten folgen Sie der Anleitung in der Benachrichtigung.
- Teile des Android-Frameworks und der Bibliotheken, z. B.
RecyclerView, enthalten Trace-Markierungen. Die Systrace-Zeitachse zeigt also, wann diese Methoden im UI-Thread ausgeführt werden und wie lange die Ausführung dauert.
Nachdem Sie sich die Systrace-Ausgabe angesehen haben, gibt es möglicherweise Methoden in Ihrer App, die Ihrer Meinung nach Ruckeln verursachen. Wenn auf der Zeitachse beispielsweise zu sehen ist, dass ein langsamer Frame durch RecyclerView verursacht wird, können Sie dem relevanten Code benutzerdefinierte Trace-Ereignisse hinzufügen und Systrace noch einmal ausführen, um weitere Informationen zu erhalten. Im neuen Systrace sehen Sie auf der Zeitachse, wann die Methoden Ihrer App aufgerufen werden und wie lange die Ausführung dauert.
Wenn Systrace keine Details dazu anzeigt, warum die Arbeit des UI-Threads so lange dauert, verwenden Sie den Android CPU Profiler, um entweder einen Stichproben- oder einen instrumentierten Methoden-Trace aufzuzeichnen. Methoden-Traces eignen sich in der Regel nicht zum Identifizieren von Verzögerungen, da sie aufgrund des hohen Aufwands falsch positive Verzögerungen erzeugen und nicht erkennen können, wann Threads ausgeführt werden und wann sie blockiert sind. Mit Methodentraces können Sie jedoch die Methoden in Ihrer App ermitteln, die am längsten dauern. Nachdem Sie diese Methoden identifiziert haben, fügen Sie Trace-Markierungen hinzu und führen Sie Systrace noch einmal aus, um zu sehen, ob diese Methoden Ruckeln verursachen.
Weitere Informationen finden Sie unter Systrace verstehen.
Benutzerdefiniertes Leistungsmonitoring
Wenn Sie Ruckeln auf einem lokalen Gerät nicht reproduzieren können, können Sie benutzerdefiniertes Leistungsmonitoring in Ihre App einbauen, um die Ursache für Ruckeln auf Geräten im Feld zu ermitteln.
Dazu müssen Sie mit FrameMetricsAggregator die Frame-Renderzeiten für bestimmte Teile Ihrer App erfassen und die Daten mit Firebase Performance Monitoring aufzeichnen und analysieren.
Weitere Informationen finden Sie unter Erste Schritte mit Performance Monitoring für Android.
Eingefrorene Frames
Eingefrorene Frames sind UI-Frames, deren Rendering länger als 700 ms dauert. Das ist ein Problem, da Ihre App während des Renderns des Frames fast eine Sekunde lang nicht auf Nutzereingaben reagiert. Wir empfehlen, Apps so zu optimieren, dass ein Frame innerhalb von 16 ms gerendert wird, um eine flüssige Benutzeroberfläche zu gewährleisten. Beim Starten der App oder beim Übergang zu einem anderen Bildschirm kann es jedoch vorkommen, dass das Zeichnen des ersten Frames länger als 16 ms dauert, da Ihre App Ansichten aufbauen, den Bildschirm layouten und das erste Zeichnen von Grund auf ausführen muss. Deshalb werden eingefrorene Frames in Android separat von langsamem Rendering erfasst. Das Rendern von Frames in Ihrer App sollte nie länger als 700 ms dauern.
Eingefrorene Frames sind eine extreme Form des langsamen Renderns. Die Vorgehensweise zur Diagnose und Behebung des Problems ist daher dieselbe.
Ruckeln bei der Bewegungserkennung
Mit FrameTimeline in Perfetto lassen sich langsame oder eingefrorene Frames nachverfolgen.
Beziehung zwischen langsamen Frames, eingefrorenen Frames und ANR-Fehlern
Langsame Frames, eingefrorene Frames und ANRs sind verschiedene Formen von Ruckeln, die in Ihrer App auftreten können. In der folgenden Tabelle finden Sie die Unterschiede.
| Langsame Frames | Eingefrorene Frames | ANRs | |
|---|---|---|---|
| Renderingzeit | Zwischen 16 ms und 700 ms | Zwischen 700 ms und 5 s | Mehr als 5 Sekunden |
| Bereich mit sichtbaren Auswirkungen auf Nutzer |
|
|
|
Langsame und eingefrorene Frames separat erfassen
Beim Starten der App oder beim Übergang zu einem anderen Bildschirm kann es vorkommen, dass das Zeichnen des ersten Frames länger als 16 ms dauert, da die App Ansichten aufbauen, den Bildschirm layouten und das erste Zeichnen von Grund auf ausführen muss.
Best Practices für die Priorisierung und Behebung von Rucklern
Beachten Sie die folgenden Best Practices, wenn Sie Verzögerungen in Ihrer App beheben möchten:
- Identifizieren und beheben Sie die am einfachsten reproduzierbaren Fälle von Ruckeln.
- ANR-Fehler priorisieren Langsame oder eingefrorene Frames können dazu führen, dass eine App träge wirkt. Bei ANRs reagiert die App jedoch nicht mehr.
- Langsames Rendering ist schwer zu reproduzieren, aber Sie können damit beginnen, eingefrorene Frames mit einer Dauer von 700 ms zu entfernen. Das passiert am häufigsten beim Starten der App oder beim Wechseln zwischen Bildschirmen.
Verzögerungen beheben
Um Verzögerung zu beheben, müssen Sie prüfen, welche Frames nicht innerhalb von 16 ms abgeschlossen werden, und nach der Ursache suchen. Prüfen Sie, ob Record View#draw oder Layout in einigen Frames ungewöhnlich lange dauert. Häufige Ursachen für Ruckeln
Um Verzögerungen zu vermeiden, sollten Sie lang andauernde Aufgaben asynchron außerhalb des UI-Threads ausführen. Achten Sie immer darauf, in welchem Thread Ihr Code ausgeführt wird, und gehen Sie vorsichtig vor, wenn Sie nicht triviale Aufgaben an den Hauptthread senden.
Wenn Sie eine komplexe und wichtige primäre Benutzeroberfläche für Ihre App haben, z. B. die zentrale Scrollliste, sollten Sie Instrumentierungstests schreiben, die langsame Rendering-Zeiten automatisch erkennen und die Tests häufig ausführen, um Regressionen zu verhindern.
Häufige Ursachen für Ruckeln
In den folgenden Abschnitten werden häufige Ursachen für Verzögerungen in Apps, die das View-System verwenden, sowie Best Practices zur Behebung dieser Probleme erläutert. Informationen zum Beheben von Leistungsproblemen mit Jetpack Compose finden Sie unter Jetpack Compose-Leistung.
Scrollbare Listen
ListView und insbesondere RecyclerView werden häufig für komplexe Scrolllisten verwendet, die am anfälligsten für Verzögerungen sind. Beide enthalten Systrace-Markierungen. Sie können also mit Systrace prüfen, ob sie zu Verzögerungen in Ihrer App beitragen. Übergeben Sie das Befehlszeilenargument -a
<your-package-name>, damit Trace-Abschnitte in RecyclerView sowie alle von Ihnen hinzugefügten Trace-Markierungen angezeigt werden. Folgen Sie gegebenenfalls der Anleitung in den Warnungen, die in der Systrace-Ausgabe generiert werden. In Systrace können Sie auf RecyclerView-Abschnitte klicken, um eine Erklärung der Arbeit zu sehen, die RecyclerView ausführt.
RecyclerView: notifyDataSetChanged()
Wenn alle Elemente in Ihrem RecyclerView neu gebunden und somit in einem Frame neu angeordnet und neu gezeichnet werden, sollten Sie nicht notifyDataSetChanged(), setAdapter(Adapter) oder swapAdapter(Adapter,
boolean)
für kleine Updates aufrufen. Diese Methoden signalisieren, dass sich der gesamte Listeninhalt ändert, und werden in Systrace als RV FullInvalidate angezeigt. Verwenden Sie stattdessen SortedList oder DiffUtil, um minimale Aktualisierungen zu generieren, wenn Inhalte geändert oder hinzugefügt werden.
Stellen Sie sich beispielsweise eine App vor, die eine neue Version einer Liste von Nachrichteninhalten von einem Server empfängt. Wenn Sie diese Informationen an den Adapter senden, ist es möglich, notifyDataSetChanged() aufzurufen, wie im folgenden Beispiel gezeigt:
Kotlin
fun onNewDataArrived(news: List<News>) { myAdapter.news = news myAdapter.notifyDataSetChanged() }
Java
void onNewDataArrived(List<News> news) { myAdapter.setNews(news); myAdapter.notifyDataSetChanged(); }
Der Nachteil ist, dass bei einer geringfügigen Änderung, z. B. wenn oben ein einzelnes Element hinzugefügt wird, die RecyclerView nicht darauf reagiert. Daher wird es angewiesen, den gesamten Status der zwischengespeicherten Elemente zu löschen, und muss alles neu binden.
Wir empfehlen die Verwendung von DiffUtil, da hier minimale Updates berechnet und gesendet werden:
Kotlin
fun onNewDataArrived(news: List<News>) { val oldNews = myAdapter.items val result = DiffUtil.calculateDiff(MyCallback(oldNews, news)) myAdapter.news = news result.dispatchUpdatesTo(myAdapter) }
Java
void onNewDataArrived(List<News> news) { List<News> oldNews = myAdapter.getItems(); DiffResult result = DiffUtil.calculateDiff(new MyCallback(oldNews, news)); myAdapter.setNews(news); result.dispatchUpdatesTo(myAdapter); }
Damit DiffUtil Ihre Listen prüfen kann, müssen Sie MyCallback als Callback-Implementierung definieren.
RecyclerView: Verschachtelte RecyclerViews
Es ist üblich, mehrere Instanzen von RecyclerView zu verschachteln, insbesondere bei einer vertikalen Liste von horizontal scrollbaren Listen. Ein Beispiel dafür sind die App-Raster auf der Google Play Store-Startseite. Das kann gut funktionieren, aber es sind auch viele Ansichten, die sich bewegen.
Wenn beim ersten Scrollen auf der Seite viele untergeordnete Elemente aufgebläht werden, sollten Sie prüfen, ob Sie RecyclerView.RecycledViewPool zwischen untergeordneten (horizontalen) Instanzen von RecyclerView freigeben. Standardmäßig hat jede RecyclerView einen eigenen Pool von Elementen. Wenn jedoch ein Dutzend itemViews gleichzeitig auf dem Bildschirm angezeigt werden, ist es problematisch, wenn itemViews nicht von den verschiedenen horizontalen Listen geteilt werden können, wenn in allen Zeilen ähnliche Ansichten angezeigt werden.
Kotlin
class OuterAdapter : RecyclerView.Adapter<OuterAdapter.ViewHolder>() { ... override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { // Inflate inner item, find innerRecyclerView by ID. val innerLLM = LinearLayoutManager(parent.context, LinearLayoutManager.HORIZONTAL, false) innerRv.apply { layoutManager = innerLLM recycledViewPool = sharedPool } return OuterAdapter.ViewHolder(innerRv) } ...
Java
class OuterAdapter extends RecyclerView.Adapter<OuterAdapter.ViewHolder> { RecyclerView.RecycledViewPool sharedPool = new RecyclerView.RecycledViewPool(); ... @Override public void onCreateViewHolder(ViewGroup parent, int viewType) { // Inflate inner item, find innerRecyclerView by ID. LinearLayoutManager innerLLM = new LinearLayoutManager(parent.getContext(), LinearLayoutManager.HORIZONTAL); innerRv.setLayoutManager(innerLLM); innerRv.setRecycledViewPool(sharedPool); return new OuterAdapter.ViewHolder(innerRv); } ...
Wenn Sie die Optimierung weiter vorantreiben möchten, können Sie auch setInitialPrefetchItemCount(int) für die LinearLayoutManager des inneren RecyclerView aufrufen. Wenn Sie beispielsweise immer 3,5 Elemente in einer Zeile sehen möchten, rufen Sie innerLLM.setInitialItemPrefetchCount(4) auf. Dadurch wird dem RecyclerView signalisiert, dass die Elemente in einer horizontalen Zeile vorab abgerufen werden müssen, wenn auf dem UI-Thread noch Zeit ist.
RecyclerView: Zu viel Inflation oder Erstellung dauert zu lange
In den meisten Fällen kann die Prefetch-Funktion in RecyclerView dazu beitragen, die Kosten für das Aufblähen zu umgehen, indem die Arbeit im Voraus erledigt wird, während der UI-Thread inaktiv ist.
Wenn Sie während eines Frames eine Inflation feststellen und nicht in einem Abschnitt mit der Bezeichnung RV Prefetch, testen Sie auf einem unterstützten Gerät und verwenden Sie eine aktuelle Version der Support Library.
Das Vorabrufen wird nur auf Geräten mit Android 5.0 (API-Level 21) und höher unterstützt.
Wenn Sie häufig sehen, dass durch die Inflation Ruckeln verursacht wird, wenn neue Elemente auf dem Bildschirm angezeigt werden, prüfen Sie, ob Sie mehr Ansichtstypen als nötig haben. Je weniger Ansichtstypen im Inhalt eines RecyclerView vorhanden sind, desto weniger Inflation ist erforderlich, wenn neue Elementtypen auf dem Bildschirm angezeigt werden. Führen Sie Ansichtstypen nach Möglichkeit zusammen, wenn das sinnvoll ist. Wenn sich zwischen den Typen nur ein Symbol, eine Farbe oder ein Textabschnitt ändert, können Sie diese Änderung zur Bindungszeit vornehmen und so die Inflation vermeiden. Dadurch wird gleichzeitig der Speicherbedarf Ihrer App verringert.
Wenn Ihre Ansichtstypen gut aussehen, können Sie versuchen, die Kosten für die Inflation zu senken.
Das kann helfen, wenn Sie unnötige Container- und Strukturansichten reduzieren. Erwägen Sie, itemViews mit ConstraintLayout zu erstellen, um die Anzahl der strukturellen Ansichten zu reduzieren.
Wenn Sie die Leistung weiter optimieren möchten und Ihre Artikelhierarchien einfach sind und Sie keine komplexen Design- und Stilfunktionen benötigen, können Sie die Konstruktoren selbst aufrufen. Oft ist es jedoch nicht sinnvoll, die Einfachheit und die Funktionen von XML aufzugeben.
RecyclerView: Bind dauert zu lange
Das Binden – also onBindViewHolder(VH,
int) – muss unkompliziert sein und darf bei allen Elementen außer den komplexesten viel weniger als eine Millisekunde dauern. Es muss einfache alte Java-Objekte (POJOs) aus den internen Artikeldaten Ihres Adapters verwenden und Setter für Ansichten in ViewHolder aufrufen. Wenn RV OnBindView lange dauert, prüfen Sie, ob Sie in Ihrem Bindungscode nur das Nötigste erledigen.
Wenn Sie einfache POJO-Objekte zum Speichern von Daten in Ihrem Adapter verwenden, können Sie das Schreiben des Bindungscodes in onBindViewHolder mithilfe der Data Binding Library vollständig vermeiden.
RecyclerView oder ListView: Layout oder Zeichnen dauert zu lange
Informationen zu Problemen mit dem Rendern und Layout finden Sie in den Abschnitten Layoutleistung und Renderleistung.
ListView: Inflation
Wenn Sie nicht aufpassen, können Sie die Papierkorbfunktion in ListView versehentlich deaktivieren. Wenn Sie jedes Mal eine Inflation sehen, wenn ein Artikel auf dem Bildschirm angezeigt wird, prüfen Sie, ob bei der Implementierung von Adapter.getView() der Parameter convertView verwendet, neu gebunden und zurückgegeben wird. Wenn Ihre getView()-Implementierung immer aufgebläht wird, profitiert Ihre App nicht vom Recycling in ListView. Die Struktur von getView() muss fast immer der folgenden Implementierung ähneln:
Kotlin
fun getView(position: Int, convertView: View?, parent: ViewGroup): View { return (convertView ?: layoutInflater.inflate(R.layout.my_layout, parent, false)).apply { // Bind content from position to convertView. } }
Java
View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { // Only inflate if no convertView passed. convertView = layoutInflater.inflate(R.layout.my_layout, parent, false) } // Bind content from position to convertView. return convertView; }
Layoutleistung
Wenn Systrace zeigt, dass das Segment Layout von Choreographer#doFrame zu viel oder zu oft arbeitet, bedeutet das, dass Sie Probleme mit der Layoutleistung haben. Die Layoutleistung Ihrer App hängt davon ab, welcher Teil der Ansichtshierarchie sich ändernde Layoutparameter oder Eingaben hat.
Layoutleistung: Kosten
Wenn die Segmente länger als einige Millisekunden sind, kann es sein, dass Sie die Worst-Case-Nesting-Leistung für RelativeLayouts oder weighted-LinearLayouts erreichen. Jedes dieser Layouts kann mehrere Mess- und Layoutdurchläufe seiner untergeordneten Elemente auslösen. Das Verschachteln kann daher zu einem O(n^2)-Verhalten in Bezug auf die Verschachtelungstiefe führen.
Vermeiden Sie RelativeLayout oder die Gewichtungsfunktion vonLinearLayout in allen Knoten der Hierarchie mit Ausnahme der untersten Blattknoten. Dazu haben Sie folgende Möglichkeiten:
- Strukturansichten neu organisieren
- Benutzerdefinierte Layoutlogik definieren Ein konkretes Beispiel finden Sie unter Layout-Hierarchien optimieren. Sie können versuchen, zu
ConstraintLayoutzu wechseln. Diese Funktion bietet ähnliche Features, ohne die Leistung zu beeinträchtigen.
Layout-Leistung: Häufigkeit
Das Layout sollte angepasst werden, wenn neue Inhalte auf dem Bildschirm angezeigt werden, z. B. wenn in RecyclerView ein neues Element eingeblendet wird. Wenn in jedem Frame ein erhebliches Layout erfolgt, animieren Sie möglicherweise das Layout, was wahrscheinlich zu ausgelassenen Frames führt.
Animationen müssen in der Regel für Zeichenattribute von View ausgeführt werden, z. B. für die folgenden:
Sie können alle diese Eigenschaften viel günstiger ändern als Layout-Eigenschaften wie Padding oder Ränder. Im Allgemeinen ist es auch viel günstiger, die Zeichenattribute einer Ansicht zu ändern, indem Sie einen Setter aufrufen, der in der nächsten Frame einen invalidate() und dann einen draw(Canvas) auslöst. Dabei werden Zeichenvorgänge für die ungültig gemachte Ansicht neu aufgezeichnet. Das ist in der Regel viel weniger aufwendig als das Layout.
Rendering-Leistung
Die Android-Benutzeroberfläche funktioniert in zwei Phasen:
- Record View#draw im UI-Thread, der
draw(Canvas)für jede ungültige Ansicht ausführt und Aufrufe in benutzerdefinierte Ansichten oder in Ihren Code auslösen kann. - DrawFrame auf dem
RenderThread, das auf dem nativenRenderThreadausgeführt wird, aber auf der Grundlage von Arbeit, die in der Phase Record View#draw generiert wurde, ausgeführt wird.
Rendering-Leistung: UI-Thread
Wenn Record View#draw lange dauert, wird häufig ein Bitmap im UI-Thread gerendert. Das Rendern von Bitmaps erfolgt über die CPU. Vermeiden Sie diese Methode daher nach Möglichkeit. Mit der Methode „Tracing“ im Android CPU Profiler können Sie herausfinden, ob dies das Problem ist.
Das Zeichnen in ein Bitmap erfolgt häufig, wenn eine App ein Bitmap vor der Anzeige dekorieren möchte, z. B. durch Hinzufügen abgerundeter Ecken:
Kotlin
val paint = Paint().apply { isAntiAlias = true } Canvas(roundedOutputBitmap).apply { // Draw a round rect to define the shape: drawRoundRect( 0f, 0f, roundedOutputBitmap.width.toFloat(), roundedOutputBitmap.height.toFloat(), 20f, 20f, paint ) paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.MULTIPLY) // Multiply content on top to make it rounded. drawBitmap(sourceBitmap, 0f, 0f, paint) setBitmap(null) // Now roundedOutputBitmap has sourceBitmap inside, but as a circle. }
Java
Canvas bitmapCanvas = new Canvas(roundedOutputBitmap); Paint paint = new Paint(); paint.setAntiAlias(true); // Draw a round rect to define the shape: bitmapCanvas.drawRoundRect(0, 0, roundedOutputBitmap.getWidth(), roundedOutputBitmap.getHeight(), 20, 20, paint); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY)); // Multiply content on top to make it rounded. bitmapCanvas.drawBitmap(sourceBitmap, 0, 0, paint); bitmapCanvas.setBitmap(null); // Now roundedOutputBitmap has sourceBitmap inside, but as a circle.
Wenn Sie diese Art von Arbeit im UI-Thread ausführen, können Sie sie stattdessen im Hintergrund im Decodierungs-Thread ausführen. In einigen Fällen, wie im vorherigen Beispiel, können Sie die Arbeit sogar zur Ziehzeit erledigen. Wenn Ihr Code für Drawable oder View etwa so aussieht:
Kotlin
fun setBitmap(bitmap: Bitmap) { mBitmap = bitmap invalidate() } override fun onDraw(canvas: Canvas) { canvas.drawBitmap(mBitmap, null, paint) }
Java
void setBitmap(Bitmap bitmap) { mBitmap = bitmap; invalidate(); } void onDraw(Canvas canvas) { canvas.drawBitmap(mBitmap, null, paint); }
Sie können es durch Folgendes ersetzen:
Kotlin
fun setBitmap(bitmap: Bitmap) { shaderPaint.shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) invalidate() } override fun onDraw(canvas: Canvas) { canvas.drawRoundRect(0f, 0f, width, height, 20f, 20f, shaderPaint) }
Java
void setBitmap(Bitmap bitmap) { shaderPaint.setShader( new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP)); invalidate(); } void onDraw(Canvas canvas) { canvas.drawRoundRect(0, 0, width, height, 20, 20, shaderPaint); }
Das ist auch für den Hintergrundschutz möglich, z. B. wenn Sie einen Farbverlauf über das Bitmap zeichnen, und für die Bildfilterung mit ColorMatrixColorFilter – zwei weitere gängige Vorgänge beim Bearbeiten von Bitmaps.
Wenn Sie aus einem anderen Grund in eine Bitmap zeichnen, z. B. um sie als Cache zu verwenden, versuchen Sie, direkt in die hardwarebeschleunigte Canvas zu zeichnen, die an Ihre View oder Drawable übergeben wird. Rufen Sie bei Bedarf auch setLayerType() mit LAYER_TYPE_HARDWARE auf, um komplexe Rendering-Ausgaben im Cache zu speichern und trotzdem die Vorteile des GPU-Renderings zu nutzen.
Rendering-Leistung: RenderThread
Einige Canvas-Vorgänge sind günstig zu erfassen, lösen aber teure Berechnungen auf der RenderThread aus. Systrace weist in der Regel mit Warnungen darauf hin.
Große Pfade animieren
Wenn Canvas.drawPath() für die hardwarebeschleunigte Canvas aufgerufen wird, die an View übergeben wurde, rendert Android diese Pfade zuerst auf der CPU und lädt sie dann auf die GPU hoch.
Bei großen Pfaden sollten Sie sie nicht Frame für Frame bearbeiten, damit sie effizient im Cache gespeichert und gezeichnet werden können.
drawPoints(), drawLines() und drawRect/Circle/Oval/RoundRect() sind effizienter und besser zu verwenden, auch wenn Sie mehr Draw Calls verwenden.
Canvas.clipPath
clipPath(Path)
löst ein teures Clipping-Verhalten aus und sollte generell vermieden werden. Zeichnen Sie nach Möglichkeit Formen, anstatt nicht rechteckige Formen zu verwenden. Sie bietet eine bessere Leistung und unterstützt Anti-Aliasing. Der folgende clipPath-Aufruf kann beispielsweise unterschiedlich ausgedrückt werden:
Kotlin
canvas.apply { save() clipPath(circlePath) drawBitmap(bitmap, 0f, 0f, paint) restore() }
Java
canvas.save(); canvas.clipPath(circlePath); canvas.drawBitmap(bitmap, 0f, 0f, paint); canvas.restore();
Stattdessen können Sie das vorherige Beispiel so ausdrücken:
Kotlin
paint.shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) // At draw time: canvas.drawPath(circlePath, mPaint)
Java
// One time init: paint.setShader(new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP)); // At draw time: canvas.drawPath(circlePath, mPaint);
Bitmap-Uploads
Unter Android werden Bitmaps als OpenGL-Texturen angezeigt. Wenn eine Bitmap zum ersten Mal in einem Frame angezeigt wird, wird sie auf die GPU hochgeladen. In Systrace wird dies als Texture upload(id) width x height angezeigt. Das kann einige Millisekunden dauern, wie in Abbildung 2 zu sehen ist. Es ist aber erforderlich, um das Bild mit der GPU anzuzeigen.
Wenn diese lange dauern, prüfen Sie zuerst die Breiten- und Höhenzahlen im Trace. Achten Sie darauf, dass die angezeigte Bitmap nicht wesentlich größer ist als der Bereich auf dem Bildschirm, in dem sie angezeigt wird. Das kostet unnötig Zeit und Speicherplatz. Im Allgemeinen bieten Bibliotheken zum Laden von Bitmaps eine Möglichkeit, eine Bitmap in der passenden Größe anzufordern.
In Android 7.0 kann der Bitmap-Ladecode, der in der Regel von Bibliotheken ausgeführt wird, prepareToDraw() aufrufen, um einen frühen Upload auszulösen, bevor er benötigt wird. So erfolgt der Upload frühzeitig, wenn das RenderThread nicht verwendet wird. Das ist nach dem Decodieren oder beim Binden eines Bitmaps an eine Ansicht möglich, sofern Sie das Bitmap kennen. Im Idealfall übernimmt das Ihre Bitmap-Ladebibliothek für Sie. Wenn Sie die Verwaltung jedoch selbst übernehmen oder sichergehen möchten, dass auf neueren Geräten keine Uploads erfolgen, können Sie prepareToDraw() in Ihrem eigenen Code aufrufen.

prepareToDraw() dekodieren.Verzögerungen bei der Thread-Planung
Der Thread-Scheduler ist der Teil des Android-Betriebssystems, der dafür zuständig ist, zu entscheiden, welche Threads im System ausgeführt werden müssen, wann sie ausgeführt werden und wie lange.
Manchmal tritt Ruckeln auf, weil der UI-Thread Ihrer App blockiert ist oder nicht ausgeführt wird. Systrace verwendet verschiedene Farben, wie in Abbildung 3 dargestellt, um anzugeben, wann ein Thread inaktiv (grau), ausführbar (blau: er kann ausgeführt werden, wurde aber noch nicht vom Scheduler ausgewählt), aktiv ausgeführt (grün) oder im nicht unterbrechbaren Schlafmodus (rot oder orange) ist. Das ist sehr nützlich, um Ruckeln zu beheben, das durch Verzögerungen bei der Threadplanung verursacht wird.
Häufig sind Binder-Aufrufe – der IPC-Mechanismus (Inter-Process Communication) unter Android – die Ursache für lange Pausen bei der Ausführung Ihrer App. In neueren Android-Versionen ist dies einer der häufigsten Gründe dafür, dass der UI-Thread nicht mehr ausgeführt wird. In der Regel besteht die Lösung darin, Funktionen zu vermeiden, die Binder-Aufrufe ausführen. Wenn es unvermeidlich ist, cachen Sie den Wert oder verschieben Sie die Arbeit in Hintergrund-Threads. Wenn Codebases größer werden, kann es passieren, dass Sie versehentlich einen Binder-Aufruf hinzufügen, indem Sie eine Low-Level-Methode aufrufen, wenn Sie nicht aufpassen. Sie können sie jedoch mit Tracing finden und beheben.
Wenn Sie Binder-Transaktionen haben, können Sie ihre Callstacks mit den folgenden adb-Befehlen erfassen:
$ adb shell am trace-ipc start
… use the app - scroll/animate ...
$ adb shell am trace-ipc stop --dump-file /data/local/tmp/ipc-trace.txt
$ adb pull /data/local/tmp/ipc-trace.txt
Manchmal können scheinbar harmlose Aufrufe wie getRefreshRate() Binder-Transaktionen auslösen und große Probleme verursachen, wenn sie häufig aufgerufen werden. Wenn Sie regelmäßig Traces erstellen, können Sie diese Probleme erkennen und beheben, sobald sie auftreten.
trace-ipc, um Binder-Aufrufe zu finden und zu entfernen.Wenn Sie keine Binder-Aktivität sehen, Ihr UI-Thread aber trotzdem nicht ausgeführt wird, sollten Sie prüfen, ob Sie auf eine Sperre oder einen anderen Vorgang aus einem anderen Thread warten. Normalerweise muss der UI-Thread nicht auf Ergebnisse von anderen Threads warten. In anderen Threads müssen Informationen dazu gepostet werden.
Objektzuweisung und automatische Speicherbereinigung
Seit der Einführung von ART als Standardlaufzeit in Android 5.0 sind Objektzuweisung und automatische Speicherbereinigung (GC) deutlich weniger problematisch. Es ist jedoch weiterhin möglich, Ihre Threads mit dieser zusätzlichen Arbeit zu belasten. Es ist in Ordnung, Speicher als Reaktion auf ein seltenes Ereignis zuzuweisen, das nicht viele Male pro Sekunde auftritt, z. B. wenn ein Nutzer auf eine Schaltfläche tippt. Denken Sie aber daran, dass jede Zuweisung mit Kosten verbunden ist. Wenn sie sich in einer engen Schleife befindet, die häufig aufgerufen wird, sollten Sie die Zuweisung vermeiden, um die Last für die automatische Speicherbereinigung zu verringern.
Systrace zeigt, ob die Garbage Collection häufig ausgeführt wird, und der Android Memory Profiler kann Ihnen zeigen, woher die Zuweisungen stammen. Wenn Sie Zuweisungen nach Möglichkeit vermeiden, insbesondere in engen Schleifen, ist es weniger wahrscheinlich, dass Probleme auftreten.
In aktuellen Android-Versionen wird die Garbage Collection in der Regel in einem Hintergrundthread mit dem Namen HeapTaskDaemon ausgeführt. Eine erhebliche Zuweisung kann bedeuten, dass mehr CPU-Ressourcen für die Garbage Collection aufgewendet werden, wie in Abbildung 5 dargestellt.
Empfehlungen für Sie
- Hinweis: Linktext wird angezeigt, wenn JavaScript deaktiviert ist
- App vergleichen
- App-Leistung messen
- Best Practices für die App-Optimierung