Langsames Rendering

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">
</ph> Systrace-Beispiel
Abbildung 1: Systrace-Beispiel.

Das Systrace-Beispiel in Abbildung 1 enthält die folgenden Informationen für Verzögerung identifizieren:

  1. 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.
  2. 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.
  3. 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
  • Scrollen von RecyclerView verhält sich abrupt
  • Auf Bildschirmen mit komplexen Animationen, die nicht richtig animiert werden
  • Beim Start der App
  • Von einem Bildschirm zur anderen wechseln, z. B. Übergang
  • Während deine App im Vordergrund ausgeführt wird, hat sie nicht reagiert auf ein Eingabeereignis oder BroadcastReceiver, z. B. Tastendruck oder Bildschirm-Tap-Ereignissen – innerhalb von fünf Sekunden.
  • Während keine Aktivität im Vordergrund läuft, Die Ausführung von BroadcastReceiver wurde innerhalb eines bestimmten Zeitraums noch nicht abgeschlossen nicht viel Zeit in Anspruch nehmen.

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 RecyclerViews. 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 nativen RenderThread 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.

<ph type="x-smartling-placeholder">
</ph> Eine App verbringt viel Zeit in einem
  Frame für das Hochladen einer großen Bitmap
Abbildung 2: Eine App verbringt viel Zeit in einem Frame. oder eine große Bitmap hochladen. Reduzieren Sie entweder die Größe oder lösen Sie sie frühzeitig aus, decodieren Sie ihn mit prepareToDraw().

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">
</ph> Hebt einen Zeitraum hervor, in dem der UI-Thread
  schläft
Abbildung 3: Markierung eines Zeitraums, in dem der UI-Thread ist Schlafenszeit.

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.

<ph type="x-smartling-placeholder">
</ph> Zeigt den UI-Thread an, der aufgrund von Binder inaktiv ist
  mit einem Wohnmobil unterwegs sind. Halten Sie die Bindungslogik fokussiert und verwenden Sie „trace-ipc“, um
  Binder-Aufrufe aufzuspüren und zu entfernen.
Abbildung 4: Der UI-Thread befindet sich aufgrund von Binder im Ruhemodus mit einem Wohnmobil unterwegs sind. Halten Sie Ihre Bindungslogik einfach und verwenden Sie trace-ipc, um Binder-Aufrufe aufzuspüren und zu entfernen.

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">
</ph> Zeigt eine Speicherbereinigung von 94 ms auf dem HeapTaskDaemon an
Abbildung 5: Eine Speicherbereinigung von 94 ms auf dem HeapTaskDaemon Thread hinzufügen.

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.