Beim UI-Rendering wird ein Frame aus Ihrer App generiert und auf einem auf dem Bildschirm. Um eine reibungslose Interaktion der Nutzer mit Ihrer App zu gewährleisten, Ihre App muss Frames in weniger als 16 ms rendern, um 60 Bilder pro Sekunde (fps) zu erreichen. Warum 60 fps bevorzugt werden, erfährst du unter Android-Leistungsmuster: Warum 60 fps? Wenn Sie versuchen, 90 fps erreichen, sinkt dieses Fenster auf 11 ms. Bei 120 fps wird es 8ms.
Wenn Sie dieses Fenster um 1 ms überschreiten, bedeutet dies nicht, dass der Frame angezeigt wird.
1 ms später, aber Choreographer
der Frame vollständig wegfällt. Wenn die Benutzeroberfläche Ihrer App langsam gerendert wird,
das System gezwungen wird, Frames zu überspringen,
und der Nutzer nimmt ein Ruckeln in der App wahr.
Das nennt man Rack. Auf dieser Seite erfahren Sie, wie Sie Verzögerungen diagnostizieren und beheben.
Wenn Sie Spiele entwickeln, die das
View
-System, dann umgehen Sie
Choreographer
. In diesem Fall ist das Frame Pacing
Hilfe zur Mediathek
OpenGL und
Vulkan-Spiele erzielen reibungsloses Rendering und
die richtige Frame-Taktung für Android.
Zur Verbesserung der App-Qualität prüft Android deine App automatisch auf Verzögerungen und die Informationen werden im Android Vitals-Dashboard angezeigt. Weitere Informationen Hier erfahren Sie, wie Sie die technische Qualität Ihrer App mit Android-Geräte Vitals
Verzögerung identifizieren
Es kann schwierig sein, in Ihrer App den Code zu finden, der zu Verzögerungen führt. Dieser Abschnitt beschreibt drei Methoden zur Identifizierung von Verzögerungen:
Mit der Sichtprüfung können Sie alle Anwendungsfälle in Ihrer App in wenigen Schritten durchgehen. Minuten, aber es bietet nicht so viele Details wie Systrace. Systrace bietet Wenn Sie Systrace jedoch für alle Anwendungsfälle in Ihrer App ausführen, mit so vielen Daten überschwemmt werden, die sich nur schwer analysieren lassen. Sowohl visuell und Systrace Verzögerungen auf Ihrem lokalen Gerät erkannt. Wenn Sie das Problem nicht reproduzieren können, Verzögerungen auf lokalen Geräten haben, können Sie eine benutzerdefinierte Leistungsüberwachung erstellen, Teile Ihrer App auf Geräten, die im Einsatz sind,
Sichtprüfung
Die Sichtprüfung hilft Ihnen, die Anwendungsfälle zu identifizieren, die zu Verzögerungen führen. Bis führen Sie eine Sichtprüfung durch, öffnen Sie Ihre App und gehen Sie Teile Ihrer App und suchen Sie auf der Benutzeroberfläche nach Verzögerungen.
Hier sind einige Tipps für visuelle Kontrollen:
- Führen Sie eine Version Ihrer App aus oder führen Sie eine Version Ihrer App aus, die nicht Debug-fähig ist. ART Die Laufzeit deaktiviert mehrere wichtige Optimierungen, um das Debugging zu unterstützen. Funktionen auswählen. Achten Sie daher darauf, sehen.
- Profil-GPU aktivieren Rendering. Das GPU-Rendering von Profilen zeigt Balken auf dem Bildschirm an, die Ihnen eine visuelle Darstellung der Zeit, die für das Rendern der Frames eines UI-Fensters benötigt wird relativ zum Benchmark von 16 ms pro Frame. Jeder Balken hat farbige Komponenten, einer Phase in der Rendering-Pipeline zuordnen, sodass Sie sehen, am längsten dauert. Wenn der Frame beispielsweise viel Zeit sollten Sie sich Ihren App-Code ansehen, der Nutzereingaben verarbeitet.
- Gehen Sie Komponenten durch, die häufige Ursachen für Verzögerungen sind, z. B.
als
RecyclerView
. - Starten Sie die App bei einer kalten Umgebung. starten.
- 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, haben Sie vielleicht eine gute Vorstellung davon, der Verzögerung in Ihrer App verursacht. Wenn Sie weitere Informationen benötigen, können Sie Systrace verwenden um der Ursache auf den Grund zu gehen.
Logo: Systrace
Obwohl Systrace ein Tool ist, das anzeigt, was das gesamte Gerät tut, kann es die Ihnen bei der Identifizierung von Verzögerungen in Ihrer App helfen. Systrace hat minimales System sodass bei der Instrumentierung realistische Verzögerungen auftreten.
Zeichnen Sie einen Trace mit Systrace auf, während Sie die funktionsgefährdenden Anwendungsfall auf Ihrem Gerät. Anweisungen zur Verwendung von Systrace finden Sie unter System-Trace über die Befehlszeile erfassen Systrace wird in Prozesse aufgeteilt Threads. Suchen Sie in Systrace nach dem Prozess Ihrer App. Dieser sieht in etwa so aus: Abbildung 1.
<ph type="x-smartling-placeholder">Das Systrace-Beispiel in Abbildung 1 enthält die folgenden Informationen für Verzögerung identifizieren:
- Systrace zeigt an, wann jeder Frame gezeichnet ist, und codiert jeden Frame farbig. langsame Renderingzeiten hervorheben. So kannst du einzelne ruckelige Frames genauer als die Sichtprüfung. Weitere Informationen finden Sie unter Benutzeroberfläche prüfen Frames und Alerts zu erkennen.
- Systrace erkennt Probleme in Ihrer App und zeigt Benachrichtigungen sowohl in einzelnen Frames und den Benachrichtigungen . Folgen Sie am besten der Anleitung in der Warnung.
- Teile des Android-Frameworks und -Bibliotheken, z. B.
RecyclerView
enthalten Trace-Markierungen. Die Funktion Systrace-Zeitachse zeigt an, wann diese Methoden auf der Benutzeroberfläche ausgeführt werden und wie lange die Ausführung dauert.
Nachdem Sie sich die Systrace-Ausgabe angesehen haben, gibt es in Ihrer App möglicherweise Methoden, die
den Sie vermuten, dass die Verzögerung verursacht wird. Zeigt die Zeitachse beispielsweise an, dass eine langsame
Frame verursacht, weil RecyclerView
lange Zeit in Anspruch nimmt, können Sie benutzerdefinierten Trace
Ereignisse an den entsprechenden Code und
Führen Sie Systrace noch einmal aus, um weitere Informationen zu erhalten. Im neuen Systrace zeigt die Zeitachse Folgendes an:
wann die Methoden Ihrer App aufgerufen werden und wie lange ihre Ausführung dauert.
Wenn Systrace keine Details dazu anzeigt, warum die Arbeit mit dem UI-Thread lange dauert und dann die Android-CPU-Plattform Profiler, um entweder ein instrumentierten Methoden-Traces. Im Allgemeinen eignen sich Methoden-Traces nicht für Verzögerungen identifizieren, da sie aufgrund von schweren und können nicht sehen, ob Threads aktiv oder blockiert sind. Aber Methoden-Traces können Ihnen helfen, die Methoden in Ihrer App zu identifizieren, die den . Fügen Sie Trace hinzu, nachdem Sie diese Methoden identifiziert haben. Markierungen und führen Sie Systrace erneut aus, ob diese Methoden Verzögerungen verursachen.
Weitere Informationen finden Sie unter Überblick über Systrace.
Benutzerdefinierte Leistungsüberwachung
Wenn Sie auf einem lokalen Gerät keine Verzögerung reproduzieren können, können Sie eine benutzerdefinierte Leistung erstellen in Ihrer App überwachen, um die Ursache für Verzögerungen auf Geräten im ein.
Erfassen Sie dazu die Frame-Renderingzeiten aus bestimmten Teilen Ihrer App mit
FrameMetricsAggregator
und die Daten mit Firebase Performance
Monitoring.
Weitere Informationen finden Sie unter Erste Schritte mit der Leistungsüberwachung für Android-Geräte
Eingefrorene Frames
Eingefrorene Frames sind UI-Frames, deren Rendering über 700 ms dauert. Dies ist ein Es liegt ein Problem vor, weil Ihre App offenbar hängen geblieben ist und nicht auf Nutzereingaben reagiert. während der Frame gerendert wird. Wir empfehlen die Optimierung um einen Frame innerhalb von 16 ms zu rendern, um eine reibungslose Benutzeroberfläche zu gewährleisten. Während der App-Nutzung oder während des Wechsels zu einem anderen Bildschirm dass das Zeichnen des Anfangsframes länger als 16 ms dauert, weil sich Ihre App nicht aufblähen muss. Ansichten, das Layout des Bildschirms und die erste Zeichnung komplett neu erstellen. Aus diesem Grund verfolgt Android eingefrorene Frames getrennt vom langsamen Rendering. Nein Das Rendern der Frames in Ihrer App sollte nie länger als 700 ms dauern.
Damit du die App-Qualität verbessern kannst, prüft Android deine App automatisch auf eingefrorenen Frames und zeigt die Informationen im Android Vitals-Dashboard an. Informationen dazu, wie die Daten erhoben werden, finden Sie unter Leistung Ihrer App überwachen mit Android Vitals verbessern.
Eingefrorene Frames sind eine extreme Form des langsamen Renderings. Die Vorgehensweise zur Diagnose und Behebung des Problems bleibt gleich.
Tracking-Verzögerung
FrameTimeline in Perfetto können Sie langsame oder eingefrorenen Frames.
Beziehung zwischen langsamen Frames, eingefrorenen Frames und ANRs
Langsame Frames, eingefrorene Frames und ANRs sind verschiedene Formen von Verzögerungen, auf die App stoßen kann. Die Unterschiede sind in der Tabelle unten aufgeführt.
Langsame Frames | Eingefrorene Frames | ANRs | |
---|---|---|---|
Renderingzeit | Zwischen 16 und 700 ms | Zwischen 700 ms und 5 s | Mehr als 5 s |
Sichtbarer Wirkungsbereich für Nutzer |
|
|
|
Langsame und eingefrorene Frames separat erfassen
Beim Starten der App oder beim Wechsel zu einem anderen Bildschirm ist das normal dass der erste Frame länger als 16 ms zum Zeichnen benötigt, die Ansichten vergrößern, das Layout des Bildschirms verändern und die erste Zeichnung von Grund auf neu erstellen.
Best Practices zum Priorisieren und Beheben von Verzögerungen
Beachten Sie die folgenden Best Practices, wenn Sie Verzögerungen in Ihrem App:
- Identifizieren und beheben Sie die am einfachsten reproduzierbaren Instanzen einer Verzögerung.
- Priorisieren Sie ANRs. Während langsame oder eingefrorene Frames eine App langsam ist, führen ANR-Fehler dazu, dass die App nicht mehr reagiert.
- Ein langsames Rendering ist schwer zu reproduzieren. Sie können jedoch damit beginnen, 700 ms eingefrorene Frames. Das geschieht meistens dann, wenn die App gestartet oder der Bildschirm gewechselt wird.
Verzögerung beheben
Prüfen Sie, welche Frames in 16 ms nicht fertig sind, und suchen Sie nach
falsch. Prüfen, ob Record View#draw
oder Layout
ungewöhnlich lange dauert in
Frames angezeigt werden. Lesen Sie den Abschnitt Häufige Ursachen von Verzögerungen für diese Probleme und
andere.
Führen Sie lang andauernde Aufgaben asynchron außerhalb des UI-Threads aus, um Verzögerungen zu vermeiden. Achten Sie immer darauf, in welchem Thread Ihr Code ausgeführt wird, und seien Sie vorsichtig, nicht triviale Aufgaben im Hauptthread posten.
Wenn Sie eine komplexe und wichtige primäre Benutzeroberfläche für Ihre App haben, z. B. die zentrale in einer scrollbaren Liste verankern: Instrumentierung Tests, die automatisch langsame Renderingzeiten erkennen und die Tests häufig ausführen, um Regressionen zu vermeiden.
Häufige Ursachen für Verzögerungen
In den folgenden Abschnitten werden häufige Ursachen von Verzögerungen in Apps erläutert, die den View
verwenden
und die Best Practices zu deren Behebung. Informationen zur Korrektur
Leistungsprobleme bei Jetpack Compose, siehe Jetpack
Leistung beim Verfassen von Texten:
Scrollbare Listen
ListView
und insbesondere
RecyclerView
: Diese werden häufig für komplexe Scrolllisten verwendet, die
anfällig für Verzögerungen. Beide enthalten Systrace-Markierungen, sodass Sie Systrace verwenden können
um zu sehen, ob sie zu Verzögerungen in Ihrer App beitragen. Übergeben Sie die Befehlszeile
Argument -a
<your-package-name>
, um Trace-Abschnitte in RecyclerView
sowie alle
Trace-Markierungen, die Sie hinzugefügt haben, damit sie angezeigt werden. Folgen Sie, falls verfügbar, den Anleitungen des
Warnungen, die in der Systrace-Ausgabe generiert wurden. In Systrace können Sie auf
RecyclerView
verarbeitete Abschnitte, um eine Erläuterung der Arbeit zu sehen RecyclerView
was macht.
RecyclerView: benachrichtigenDataSetChanged()
Wenn du siehst, dass alle Elemente in RecyclerView
neu gebunden werden
und in einem Frame neu gezeichnet haben. Achten Sie darauf, dass Sie nicht
notifyDataSetChanged()
setAdapter(Adapter)
oder swapAdapter(Adapter,
boolean)
für kleine Updates. Diese Methoden signalisieren, dass Änderungen am gesamten
und in Systrace als RV FullIn Display angezeigt werden. Verwenden Sie stattdessen
SortedList
oder
DiffUtil
zum Generieren
nur minimale Updates,
wenn Inhalte geändert oder hinzugefügt werden.
Angenommen, eine App erhält eine neue Version einer Nachrichtenliste.
von einem Server abgerufen werden. Wenn Sie diese Informationen an den Adapter senden,
notifyDataSetChanged()
kann wie im folgenden Beispiel aufgerufen werden:
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, wenn es nur eine geringfügige Änderung gibt, z. B. ein einzelner Artikel
hinzugefügt, weiß das RecyclerView
nicht. Daher wird gesagt,
den gesamten Status des im Cache gespeicherten Elements
und muss daher alles neu binden.
Wir empfehlen die Verwendung von DiffUtil
. Damit werden nur minimale Aktualisierungen berechnet und ausgelöst.
für dich:
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
deine Listen prüfen kann, definiere MyCallback
als
Callback
Implementierung.
RecyclerView: verschachtelte RecyclerViews
Es ist üblich, mehrere Instanzen von RecyclerView
zu verschachteln, insbesondere mit einer
vertikale Liste horizontal scrollbarer Listen. Ein Beispiel dafür sind die Raster,
von Apps auf der Play Store-Hauptseite. Das kann großartig funktionieren, aber es ist auch eine Menge
sich bewegende Ansichten.
Wenn sich viele innere Elemente
aufblähen, wenn Sie zum ersten Mal auf der Seite nach unten scrollen,
sollten Sie überprüfen, ob Sie
RecyclerView.RecycledViewPool
zwischen inneren (horizontalen) Instanzen von RecyclerView
. Standardmäßig werden alle
RecyclerView
hat einen eigenen Pool von Elementen. Im Fall von Dutzenden von
itemViews
auf dem Bildschirm zu sehen ist, ist es problematisch, wenn itemViews
nicht
werden auch für die verschiedenen horizontalen Listen übernommen, wenn alle Zeilen ähnlich angezeigt werden.
Arten von Ansichten.
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); } ...
Falls Sie weitere Optimierungen vornehmen möchten, rufen Sie uns an:
setInitialPrefetchItemCount(int)
am
LinearLayoutManager
des inneren RecyclerView
s. Wenn Sie zum Beispiel immer 3, 5 Elemente sichtbar haben,
Rufen Sie in Folge innerLLM.setInitialItemPrefetchCount(4)
auf. Dies signalisiert den
RecyclerView
dass eine horizontale Reihe
versuchen, die darin enthaltenen Elemente vorab abzurufen, wenn im UI-Thread Zeit übrig ist.
RecyclerView: Zu starke Inflation oder Erstellung dauert zu lange
In den meisten Fällen kann die Prefetch-Funktion in RecyclerView
das Problem umgehen.
der Inflation, indem die Arbeit
im Leerlauf des UI-Threads im Voraus erledigt wird.
Wenn Sie die Inflation während eines Frames und nicht im Abschnitt RV
Prefetch: Testen Sie auf einem unterstützten Gerät und verwenden Sie eine aktuelle
Version der Supportbibliothek.
Der Prefetch wird nur unter Android 5.0 API Level 21 und höher unterstützt.
Wenn Sie häufig eine Inflation bemerken, die zu Verzögerungen führt, wenn neue Elemente auf dem Bildschirm erscheinen,
dass Sie nicht mehr
Ansichtstypen als nötig haben. Je weniger Ansichtstypen
RecyclerView
ist, desto weniger Inflation muss bei neuen
auf dem Bildschirm erscheinen. Führen Sie nach Möglichkeit Ansichtstypen zusammen. Wenn
nur ein Symbol, eine Farbe oder ein Text zwischen den Typen ändert, können Sie
Änderung beim Binden und Vermeiden einer Inflation, da dadurch der Arbeitsspeicher der App reduziert wird
gleichzeitig Fußabdruck.
Wenn Ihre Aufruftypen gut aussehen, sollten Sie in Betracht ziehen, die Inflationskosten zu senken.
Das Reduzieren unnötiger Container- und Strukturansichten kann helfen. Erwägen Sie,
itemViews
mit ConstraintLayout
,
was dazu beitragen kann,
strukturelle Ansichten zu reduzieren.
Wenn Sie die Leistung weiter optimieren möchten und Ihre Elementhierarchien und Sie benötigen keine komplexen Designs und Stilelemente, können Sie die Konstruktoren. Oft lohnt es sich jedoch nicht, der Einfachheit und der Funktionen von XML.
RecyclerView: Bindung dauert zu lange
Bind, d. h. onBindViewHolder(VH,
int)
, muss einfach sein und weniger als eine Millisekunde für
mit Ausnahme der komplexesten Elemente. Er muss ein einfaches altes Java-Objekt (POJO) verwenden.
aus den internen Artikeldaten Ihres Adapters und Call Setter in Ansichten im
ViewHolder
Wenn RV OnBindView lange dauert, überprüfen Sie, ob Sie
mit minimaler Arbeit in Ihrem Bind-Code.
Wenn Sie einfache POJO-Objekte verwenden, um Daten in Ihrem Adapter zu speichern, können Sie
Sie vermeiden das Schreiben des Bindungscodes in onBindViewHolder
mithilfe der Methode
Data Binding Library.
RecyclerView oder ListView: Layout oder Zeichnen dauert zu lange
Informationen zu Zeichen- und Layoutproblemen finden Sie unter Layoutleistung und Bereiche Rendering-Leistung.
Listenansicht: Inflation
Wenn du nicht vorsichtig bist, kannst du das Recycling in ListView
versehentlich deaktivieren. Wenn
jedes Mal, wenn ein Artikel auf dem Bildschirm erscheint, Inflation zu sehen,
Implementierung von
Adapter.getView()
das nachdenkt, neu bindet und den convertView
-Parameter zurückgibt. Wenn Ihr
Die getView()
-Implementierung erhöht sich ständig, deine App hat nicht die folgenden Vorteile:
Recycling in ListView
. Die Struktur deines getView()
muss fast immer so
der folgenden Implementierung:
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 anzeigt, dass das Segment Layout von Choreographer#doFrame
wenn zu oft oder zu oft gearbeitet wird,
Probleme mit der Leistung. Die Layoutleistung Ihrer App hängt davon ab,
der Ansichtshierarchie sich ändernde Layoutparameter oder Eingaben enthält.
Layoutleistung: Kosten
Wenn die Segmente länger als ein paar Millisekunden sind, ist es möglich, dass Sie
die Worst-Case-Verschachtelungsleistung
RelativeLayouts
oder
weighted-LinearLayouts
Jede von
können diese Layouts mehrere Messungs- und Layout-Durchläufe ihrer untergeordneten Elemente auslösen,
eine Verschachtelung kann zum O(n^2)
-Verhalten in Bezug auf die Tiefe der Verschachtelung führen.
Vermeide möglichst RelativeLayout
oder die Gewichtung von LinearLayout
bei allen bis auf
die untersten Blattknoten der Hierarchie. Dazu haben Sie folgende Möglichkeiten:
- Strukturelle Ansichten neu anordnen
- Definieren Sie eine benutzerdefinierte Layoutlogik. Weitere Informationen finden Sie unter
Layouthierarchien optimieren
für ein konkretes Beispiel. Sie können versuchen,
ConstraintLayout
bietet ähnliche Funktionen, aber ohne Leistungseinbußen.
Layoutleistung: Häufigkeit
Das Layout wird erwartet, wenn neue Inhalte auf dem Bildschirm erscheinen, z. B. wenn
Ein neues Element wird in RecyclerView
sichtbar. Wenn wichtiges Layout
in jedem Frame passiert, ist es möglich, dass das Layout animiert wird.
die wahrscheinlich zu
verworfenen Frames führen.
Im Allgemeinen müssen Animationen anhand der Zeicheneigenschaften von View
wie dem
Folgendes:
All diese Änderungen lassen sich viel kostengünstiger als Layouteigenschaften ändern, z. B.
oder Ränder festlegen. In der Regel ist es auch weitaus kostengünstiger,
einer Ansicht durch Aufrufen eines Setters, der eine
invalidate()
, gefolgt von
draw(Canvas)
im nächsten Frame. Dadurch werden Zeichenvorgänge für die Ansicht neu aufgezeichnet, die
und ist im Allgemeinen wesentlich günstiger als das Layout.
Rendering-Leistung
Die Android-Benutzeroberfläche gliedert sich in zwei Phasen:
- Record View#draw im Benutzeroberflächen-Thread, der
draw(Canvas)
auf jedem ungültige Ansicht an und kann Aufrufe an benutzerdefinierte Ansichten oder Ihren Code auslösen. - DrawFrame für den
RenderThread
, der auf dem nativenRenderThread
ausgeführt wird basiert jedoch auf der Arbeit in der Phase Record View#draw.
Rendering-Leistung: UI-Thread
Wenn Record View#draw sehr lange dauert, ist es üblich, dass eine Bitmap dargestellt wird. Für das Zeichnen auf eine Bitmap wird CPU-Rendering verwendet. sollten Sie dies nach Möglichkeit vermeiden. Sie können Methoden-Tracing mit der Android-App CPU Profiler, um zu sehen, ob dies das Problem zu lösen.
Eine Bitmap wird oft dann gerendert, wenn eine App eine Bitmap vor dem dargestellt werden – manchmal auch eine Form des Hinzufügens von abgerundeten 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 am UI-Thread machen, können Sie stattdessen
auf den Decodierungs-Thread im Hintergrund. In einigen Fällen, wie z. B.
können Sie die Arbeit auch beim Zeichnen erledigen. Wenn Ihre
Drawable
oder
Der View
-Code sieht in etwa so aus:
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 ihn wie folgt 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); }
Sie können dies auch für den Hintergrundschutz tun, z. B. beim Zeichnen eines Farbverlaufs.
über der Bitmap und Bildfilterung mit
ColorMatrixColorFilter
: zwei
weitere gängige Vorgänge beim
Ändern von Bitmaps durchgeführt.
Wenn Sie aus einem anderen Grund auf eine Bitmap zeichnen, z. B.
-Cache – versuchen Sie, in den hardwarebeschleunigten Canvas
zu zeichnen, der an Ihre View
- oder
Drawable
direkt. Erwägen Sie bei Bedarf auch,
setLayerType()
mit
LAYER_TYPE_HARDWARE
um komplexe Rendering-Ausgaben im Cache zu speichern und gleichzeitig
das GPU-Rendering zu nutzen.
Rendering-Leistung: RenderThread
Einige Canvas
-Vorgänge lassen sich preiswert aufzeichnen, lösen aber eine teure Berechnung aus
am RenderThread
. Systrace weist diese im Allgemeinen mit Warnungen darauf hin.
Große Pfade animieren
Wann?
Canvas.drawPath()
wird auf der hardwarebeschleunigten Canvas
aufgerufen,
auf View
, zeichnet Android diese Pfade zuerst auf der CPU und lädt sie dann auf die GPU hoch.
Wenn Sie große Pfade haben, bearbeiten Sie sie nicht von Frame zu Frame, damit sie
im Cache gespeichert und effizient abgerufen werden können.
drawPoints()
,
drawLines()
und drawRect/Circle/Oval/RoundRect()
sind effizienter und
auch dann besser, wenn Sie
mehr Zeichenaufrufe verwenden.
Canvas.clipPath
clipPath(Path)
löst ein kostspieliges Clipping-Verhalten aus und muss im Allgemeinen vermieden werden. Wann?
sollten Sie Formen zeichnen, anstatt sie auf Nicht-Rechtecke zu beschneiden. Es
funktioniert besser und unterstützt Kantenglättung. Beispiel:
clipPath
-Aufruf kann 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();
Geben Sie stattdessen das vorherige Beispiel so aus:
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
Android zeigt Bitmaps als OpenGL-Texturen an. Das erste Mal, dass eine Bitmap in einem Frame angezeigt wird, wird es auf die GPU hochgeladen. Sie können dies in Systrace als Breite × Höhe des Texturuploads(ID): Dies kann einige Millisekunden dauern, wie in Abbildung 2 dargestellt, aber das Bild muss mit der GPU angezeigt werden.
Sollte es lange dauern, überprüfen Sie zuerst die Werte für Breite und Höhe in der Trace. Achten Sie darauf, dass die angezeigte Bitmap nicht wesentlich größer ist als auf dem Bildschirm angezeigt wird. Andernfalls verschwendet ihr Upload-Zeit und zu speichern. Im Allgemeinen bieten Bitmap-Ladebibliotheken eine Möglichkeit zum Anfordern eines Bitmap der richtigen Größe.
Unter Android 7.0 kann der Code zum Laden der Bitmap-Datei – in der Regel von Bibliotheken erstellt – die Funktion
prepareToDraw()
bis
einen frühzeitigen Upload auslösen,
bevor er benötigt wird. So wird der Upload frühzeitig abgeschlossen.
während RenderThread
inaktiv ist. Dies ist nach der Decodierung oder beim Binden möglich.
Bitmap in eine Ansicht umwandeln, wenn Sie die Bitmap kennen. Idealerweise sollte Ihre Bitmap
ist das für Sie erledigt. Wenn Sie Ihre eigene
Bibliothek verwalten oder sicherstellen möchten,
auf neueren Geräten keine Uploads, du kannst prepareToDraw()
selbst anrufen
Code.
Verzögerungen bei der Thread-Planung
Der Thread-Planer ist der Teil des Android-Betriebssystems, der für die Sie entscheiden, welche Threads im System wann und wie lange ausgeführt werden müssen.
Manchmal treten Verzögerungen auf, weil der UI-Thread Ihrer App blockiert ist oder nicht ausgeführt wird. Systrace verwendet verschiedene Farben (siehe Abbildung 3), um anzuzeigen, wann ein Thread ist sleeping (grau), runnable (blau: kann ausgeführt werden, wird aber nicht vom Planer, der noch ausgeführt werden soll), aktiv ausgeführt (grün) oder sich im unterbrechungsfreien Ruhemodus befindet (rot oder orange). Dies ist äußerst hilfreich zum Debuggen von Verzögerungsproblemen, die verursacht durch Verzögerungen bei der Thread-Planung.
<ph type="x-smartling-placeholder">Häufig werden Binder-Aufrufe – der IPC-Mechanismus (Inter-Process-Communication) Android führen zu langen Pausen bei der Ausführung Ihrer App. Bei neueren Android-Versionen Dies ist einer der häufigsten Gründe, warum der UI-Thread nicht mehr ausgeführt wird. Im Allgemeinen besteht die Lösung darin, keine Funktionen aufzurufen, die Binder-Aufrufe ausführen. Wenn es den Wert im Cache zu speichern oder die Arbeit in Hintergrundthreads zu verschieben. Als Codebasis größer werden, können Sie versehentlich einen Binder-Aufruf hinzufügen, indem Sie wenn Sie nicht vorsichtig sind. Sie können sie jedoch mit Tracing finden und beheben.
Wenn Sie Binder-Transaktionen haben, können Sie deren Aufrufstacks mit der Methode
folgenden adb
-Befehlen:
$ 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
Anrufe, die harmlos wirken,
getRefreshRate()
, kann
Binder-Transaktionen auslösen und große Probleme verursachen, wenn sie aufgerufen werden
häufig auftreten. Das regelmäßige Tracing kann Ihnen helfen, diese Probleme zu finden und zu beheben,
angezeigt werden.
Wenn Sie keine Binder-Aktivität sehen, Ihr UI-Thread aber trotzdem nicht ausgeführt wird, gehen Sie so vor: warten Sie nicht auf eine Sperre oder einen anderen Vorgang aus einem anderen Thread. Normalerweise muss der UI-Thread nicht auf Ergebnisse von anderen Threads warten. Andere Threads müssen Informationen darin veröffentlichen.
Objektzuweisung und automatische Speicherbereinigung
Objektzuweisung und automatische Speicherbereinigung (GC) sind wesentlich weniger problematisch seit ART als Standardlaufzeit in Android 5.0 eingeführt wurde, aber es ist immer noch die Möglichkeit, Ihre Fäden mit diesem Zusatzaufwand zu wiegen. Es ist in Ordnung, als Reaktion auf ein seltenes Ereignis reagieren, das nicht oft pro Sekunde eintritt, z. B. Nutzende auf eine Schaltfläche tippen. Denken Sie jedoch daran, dass jede Zuweisung Kosten mit sich bringt. Wenn sie sich in einer engen Schleife befindet, die häufig aufgerufen wird, sollten Sie erwägen, um die Speicherbelastung zu reduzieren.
Systrace zeigt an, ob GC häufig ausgeführt wird, und ob der Android Memory Profiler kann Ihnen zeigen, wo Zuweisungen von denen Sie stammen. Wenn Sie Zuweisungen nach Möglichkeit vermeiden, insbesondere bei engen werden Probleme seltener auftreten.
<ph type="x-smartling-placeholder">Bei neueren Android-Versionen wird GC in der Regel auf einem Hintergrundthread ausgeführt, der HeapTaskDaemon Starke Zuweisungsmengen können mehr CPU bedeuten für GC aufgewendete Ressourcen, wie in Abbildung 5 dargestellt.
Empfehlungen für dich
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Benchmarking Ihrer App
- App-Leistung messen
- Best Practices für die App-Optimierung