Hardwarebeschleunigung (Ansichten)

Konzepte und Jetpack Compose-Implementierung

Ab Android 3.0 (API-Level 11) unterstützt die Android-2D-Rendering-Pipeline die Hardwarebeschleunigung. Das bedeutet, dass alle Zeichenvorgänge, die auf dem Canvas eines View ausgeführt werden, die GPU verwenden. Da für die Hardwarebeschleunigung mehr Ressourcen erforderlich sind, verbraucht Ihre App mehr RAM.

Die Hardwarebeschleunigung ist standardmäßig aktiviert, wenn Ihr Ziel-API-Level >=14 ist. Sie kann aber auch explizit aktiviert werden. Wenn Ihre Anwendung nur Standardansichten und Drawables verwendet, sollte das globale Aktivieren keine negativen Zeicheneffekte verursachen. Da die Hardwarebeschleunigung jedoch nicht für alle 2D-Zeichnungsvorgänge unterstützt wird, kann sich die Aktivierung auf einige Ihrer benutzerdefinierten Ansichten oder Zeichenaufrufe auswirken. Probleme äußern sich in der Regel in unsichtbaren Elementen, Ausnahmen oder falsch gerenderten Pixeln. Um dieses Problem zu beheben, bietet Android die Möglichkeit, die Hardwarebeschleunigung auf mehreren Ebenen zu aktivieren oder zu deaktivieren. Weitere Informationen finden Sie unter Hardwarebeschleunigung steuern.

Wenn Ihre Anwendung benutzerdefinierte Zeichnungen ausführt, testen Sie sie auf echten Hardwaregeräten mit aktivierter Hardwarebeschleunigung, um Probleme zu finden. Im Abschnitt Unterstützung für Zeichenvorgänge werden bekannte Probleme mit der Hardwarebeschleunigung und Möglichkeiten zur Umgehung dieser Probleme beschrieben.

Siehe auch OpenGL mit den Framework-APIs und RenderScript.

Hardwarebeschleunigung steuern

Sie können die Hardwarebeschleunigung auf den folgenden Ebenen steuern:

  • Anwendung
  • Aktivität
  • Fenster
  • Ansehen

Anwendungsebene

Fügen Sie in der Android-Manifestdatei dem Tag <application> das folgende Attribut hinzu, um die Hardwarebeschleunigung für die gesamte Anwendung zu aktivieren:

<application android:hardwareAccelerated="true" ...>

Aktivitätslevel

Wenn Ihre Anwendung bei global aktivierter Hardwarebeschleunigung nicht ordnungsgemäß funktioniert, können Sie diese auch für einzelne Aktivitäten ein- und ausschalten. Wenn Sie die Hardwarebeschleunigung auf Aktivitätsebene aktivieren oder deaktivieren möchten, können Sie das Attribut android:hardwareAccelerated für das Element <activity> verwenden. Im folgenden Beispiel wird die Hardwarebeschleunigung für die gesamte Anwendung aktiviert, aber für eine Aktivität deaktiviert:

<application android:hardwareAccelerated="true">
    <activity ... />
    <activity android:hardwareAccelerated="false" />
</application>

Fensterstufe

Wenn Sie noch mehr Kontrolle benötigen, können Sie die Hardwarebeschleunigung für ein bestimmtes Fenster mit dem folgenden Code aktivieren:

Kotlin

window.setFlags(
        WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
        WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
)

Java

getWindow().setFlags(
    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);

Ansichtsebene

Sie können die Hardwarebeschleunigung für eine einzelne Ansicht zur Laufzeit mit dem folgenden Code deaktivieren:

Kotlin

myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null)

Java

myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

Feststellen, ob eine Ansicht hardwarebeschleunigt ist

Manchmal ist es für eine Anwendung nützlich zu wissen, ob sie derzeit hardwarebeschleunigt ist, insbesondere für Dinge wie benutzerdefinierte Ansichten. Das ist besonders nützlich, wenn Ihre Anwendung viele benutzerdefinierte Zeichnungen ausführt und nicht alle Vorgänge von der neuen Rendering-Pipeline richtig unterstützt werden.

Es gibt zwei Möglichkeiten, um zu prüfen, ob die Anwendung hardwarebeschleunigt ist:

Wenn Sie diese Prüfung in Ihrem Zeichencode durchführen müssen, verwenden Sie nach Möglichkeit Canvas.isHardwareAccelerated anstelle von View.isHardwareAccelerated. Wenn eine Ansicht an ein hardwarebeschleunigtes Fenster angehängt ist, kann sie weiterhin mit einem nicht hardwarebeschleunigten Canvas gezeichnet werden. Das ist beispielsweise der Fall, wenn eine Ansicht zu Caching-Zwecken in eine Bitmap gezeichnet wird.

Android-Zeichenmodelle

Wenn die Hardwarebeschleunigung aktiviert ist, verwendet das Android-Framework ein neues Zeichenmodell, bei dem Anzeigelisten verwendet werden, um Ihre Anwendung auf dem Bildschirm zu rendern. Um Anzeigelisten und ihre Auswirkungen auf Ihre Anwendung vollständig zu verstehen, ist es hilfreich, zu wissen, wie Android Ansichten ohne Hardwarebeschleunigung zeichnet. In den folgenden Abschnitten werden die softwarebasierten und hardwarebeschleunigten Zeichenmodelle beschrieben.

Softwarebasiertes Zeichenmodell

Im Software-Zeichnungsmodell werden Ansichten in zwei Schritten gezeichnet:

  1. Hierarchie entwerten
  2. Hierarchie zeichnen

Immer wenn eine Anwendung einen Teil ihrer Benutzeroberfläche aktualisieren muss, ruft sie invalidate() (oder eine ihrer Varianten) für jede Ansicht auf, deren Inhalt sich geändert hat. Die Invalidierungsmeldungen werden in der gesamten Ansichtshierarchie weitergegeben, um die Bereiche des Bildschirms zu berechnen, die neu gezeichnet werden müssen (der „dirty region“). Das Android-System zeichnet dann alle Ansichten in der Hierarchie, die sich mit dem Bereich überschneiden, der neu gezeichnet werden muss. Dieses Zeichenmodell hat jedoch zwei Nachteile:

  • Erstens muss bei jedem Zeichenvorgang viel Code ausgeführt werden. Wenn Ihre Anwendung beispielsweise invalidate für eine Schaltfläche aufruft und sich diese Schaltfläche über einer anderen Ansicht befindet, zeichnet das Android-System die Ansicht neu, obwohl sie sich nicht geändert hat.

  • Das zweite Problem besteht darin, dass das Zeichenmodell Fehler in Ihrer Anwendung verbergen kann. Da das Android-System Ansichten neu zeichnet, wenn sie sich mit dem Bereich überschneiden, der neu gezeichnet werden muss, kann es sein, dass eine Ansicht, deren Inhalt Sie geändert haben, neu gezeichnet wird, obwohl invalidate nicht für sie aufgerufen wurde. In diesem Fall sind Sie darauf angewiesen, dass eine andere Ansicht ungültig wird, damit das richtige Verhalten erzielt wird. Dieses Verhalten kann sich bei jeder Änderung Ihrer Anwendung ändern. Daher sollten Sie invalidate immer für Ihre benutzerdefinierten Ansichten aufrufen, wenn Sie Daten oder den Status ändern, die sich auf den Zeichencode der Ansicht auswirken.

Hardwarebeschleunigtes Zeichenmodell

Das Android-System verwendet weiterhin invalidate und draw, um Bildschirmaktualisierungen anzufordern und Ansichten zu rendern, aber die eigentliche Darstellung wird anders gehandhabt. Anstatt die Zeichenbefehle sofort auszuführen, zeichnet das Android-System sie in Anzeigelisten auf, die die Ausgabe des Zeichencodes der Ansichtshierarchie enthalten. Eine weitere Optimierung besteht darin, dass das Android-System nur Anzeigelisten für Ansichten aufzeichnen und aktualisieren muss, die durch einen invalidate-Aufruf als „dirty“ markiert wurden. Ansichten, die nicht ungültig gemacht wurden, können neu gezeichnet werden, indem die zuvor aufgezeichnete Anzeigeliste noch einmal ausgegeben wird. Das neue Zeichenmodell umfasst drei Phasen:

  1. Hierarchie entwerten

  2. Anzeigelisten aufzeichnen und aktualisieren

  3. Anzeigen von Listen zeichnen

Bei diesem Modell können Sie sich nicht darauf verlassen, dass für eine Ansicht, die den unsauberen Bereich schneidet, die Methode draw ausgeführt wird. Damit das Android-System die Anzeigeliste einer Ansicht aufzeichnet, müssen Sie invalidate aufrufen. Wenn Sie das vergessen, sieht eine Ansicht auch nach einer Änderung gleich aus.

Die Verwendung von Anzeigelisten wirkt sich auch positiv auf die Animationsleistung aus, da zum Festlegen bestimmter Eigenschaften wie Alpha oder Rotation die Zielansicht nicht ungültig gemacht werden muss (das erfolgt automatisch). Diese Optimierung gilt auch für Ansichten mit Anzeigelisten (alle Ansichten, wenn Ihre Anwendung hardwarebeschleunigt ist). Angenommen, es gibt ein LinearLayout, das ein ListView über einem Button enthält. Die Anzeigeliste für LinearLayout sieht so aus:

  • DrawDisplayList(ListView)
  • DrawDisplayList(Button)

Angenommen, Sie möchten die Deckkraft des ListView ändern. Nachdem setAlpha(0.5f) für ListView aufgerufen wurde, enthält die Displayliste Folgendes:

  • SaveLayerAlpha(0.5)

  • DrawDisplayList(ListView)

  • Restore

  • DrawDisplayList(Button)

Der komplexe Zeichencode von ListView wurde nicht ausgeführt. Stattdessen wurde nur die Anzeigeliste des viel einfacheren LinearLayout aktualisiert. In einer Anwendung ohne aktivierte Hardwarebeschleunigung wird der Zeichencode sowohl für die Liste als auch für das übergeordnete Element noch einmal ausgeführt.

Unterstützung für Zeichenvorgänge

Bei Hardwarebeschleunigung unterstützt die 2D-Rendering-Pipeline die am häufigsten verwendeten Canvas-Zeichnungsvorgänge sowie viele weniger häufig verwendete Vorgänge. Alle Zeichenvorgänge, die zum Rendern von Anwendungen, die mit Android ausgeliefert werden, Standard-Widgets und ‑Layouts sowie gängigen erweiterten visuellen Effekten wie Spiegelungen und gekachelten Texturen verwendet werden, werden unterstützt.

In der folgenden Tabelle wird der Supportgrad verschiedener Vorgänge für die einzelnen API-Ebenen beschrieben:

Erstes unterstütztes API-Level
Canvas
drawBitmapMesh() (colors-Array) 18
drawPicture() 23
drawPosText() 16
drawTextOnPath() 16
drawVertices() 29
setDrawFilter() 16
clipPath() 18
clipRegion() 18
clipRect(Region.Op.XOR) 18
clipRect(Region.Op.Difference) 18
clipRect(Region.Op.ReverseDifference) 18
clipRect() mit Drehung/Perspektive 18
Farbe
setAntiAlias() (für Text) 18
setAntiAlias() (für Linien) 16
setFilterBitmap() 17
setLinearText()
setMaskFilter()
setPathEffect() (für Linien) 28
setShadowLayer() (außer Text) 28
setStrokeCap() (für Linien) 18
setStrokeCap() (für Punkte) 19
setSubpixelText() 28
Xfermode
PorterDuff.Mode.DARKEN (Framebuffer) 28
PorterDuff.Mode.LIGHTEN (Framebuffer) 28
PorterDuff.Mode.OVERLAY (Framebuffer) 28
Shader
ComposeShader in ComposeShader 28
Shader desselben Typs in ComposeShader 28
Lokale Matrix in ComposeShader 18

Canvas-Skalierung

Die hardwarebeschleunigte 2D-Rendering-Pipeline wurde zuerst entwickelt, um das Zeichnen ohne Skalierung zu unterstützen. Bei einigen Zeichenvorgängen wird die Qualität bei höheren Skalierungswerten jedoch erheblich beeinträchtigt. Diese Vorgänge werden als Texturen implementiert, die im Maßstab 1.0 gezeichnet und von der GPU transformiert werden. Ab API-Ebene 28 können alle Zeichenvorgänge problemlos skaliert werden.

In der folgenden Tabelle sehen Sie, wann die Implementierung geändert wurde, um große Skalen korrekt zu verarbeiten:

Zeichnungsvorgang, der skaliert werden soll Erstes unterstütztes API-Level
drawText() 18
drawPosText() 28
drawTextOnPath() 28
Einfache Formen 17
Komplexe Formen 28
drawPath() 28
Schattenebene 28

Wenn Ihre Anwendung von einer dieser fehlenden Funktionen oder Einschränkungen betroffen ist, können Sie die Hardwarebeschleunigung für den betroffenen Teil Ihrer Anwendung deaktivieren, indem Sie setLayerType(View.LAYER_TYPE_SOFTWARE, null) aufrufen. So können Sie die Hardwarebeschleunigung weiterhin überall nutzen. Weitere Informationen zum Aktivieren und Deaktivieren der Hardwarebeschleunigung auf verschiedenen Ebenen in Ihrer Anwendung finden Sie unter Hardwarebeschleunigung steuern.

Ebenen ansehen

In allen Android-Versionen konnten Ansichten in Off-Screen-Puffer gerendert werden, entweder über den Zeichencache einer Ansicht oder über Canvas.saveLayer. Off-Screen-Puffer oder ‑Ebenen haben mehrere Anwendungsfälle. Sie können sie verwenden, um die Leistung bei der Animation komplexer Ansichten zu verbessern oder Kompositionseffekte anzuwenden. So können Sie beispielsweise mit Canvas.saveLayer Einblendeffekte implementieren, indem Sie eine Ansicht vorübergehend in einer Ebene rendern und sie dann mit einem Deckkraftfaktor wieder auf dem Bildschirm zusammenfügen.

Ab Android 3.0 (API-Level 11) haben Sie mehr Kontrolle darüber, wie und wann Sie Ebenen mit der Methode View.setLayerType verwenden. Diese API akzeptiert zwei Parameter: den Typ der Ebene, die Sie verwenden möchten, und ein optionales Paint-Objekt, das beschreibt, wie die Ebene zusammengesetzt werden soll. Mit dem Parameter Paint können Sie Farbfilter, spezielle Mischmodi oder die Deckkraft auf eine Ebene anwenden. Für eine Ansicht kann einer von drei Layertypen verwendet werden:

  • LAYER_TYPE_NONE: Die Ansicht wird normal gerendert und nicht durch einen Off-Screen-Puffer unterstützt. Das ist das Standardverhalten.

  • LAYER_TYPE_HARDWARE: Die Ansicht wird in Hardware in eine Hardware-Textur gerendert, wenn die Anwendung hardwarebeschleunigt ist. Wenn die Anwendung nicht hardwarebeschleunigt ist, verhält sich dieser Layer-Typ wie LAYER_TYPE_SOFTWARE.

  • LAYER_TYPE_SOFTWARE: Die Ansicht wird in Software in eine Bitmap gerendert.

Welchen Layer-Typ Sie verwenden, hängt von Ihrem Ziel ab:

  • Leistung: Mit einem Hardware-Layer-Typ wird eine Ansicht in eine Hardware-Textur gerendert. Sobald eine Ansicht in einer Ebene gerendert wurde, muss ihr Zeichencode erst ausgeführt werden, wenn die Ansicht invalidate aufruft. Einige Animationen, z. B. Alpha-Animationen, können dann direkt auf die Ebene angewendet werden. Das ist sehr effizient für die GPU.

  • Visuelle Effekte: Verwenden Sie einen Hardware- oder Software-Layer-Typ und ein Paint, um einer Ansicht spezielle visuelle Effekte hinzuzufügen. Sie können beispielsweise eine Ansicht in Schwarzweiß zeichnen, indem Sie eine ColorMatrixColorFilter verwenden.

  • Kompatibilität: Mit einem Software-Layer-Typ können Sie erzwingen, dass eine Ansicht in der Software gerendert wird. Wenn eine Ansicht, die hardwarebeschleunigt ist (z. B. wenn Ihre gesamte Anwendung hardwarebeschleunigt ist), Probleme beim Rendern aufweist, ist dies eine einfache Möglichkeit, die Einschränkungen der Hardware-Rendering-Pipeline zu umgehen.

Ebenen und Animationen ansehen

Hardwareebenen können schnellere und flüssigere Animationen ermöglichen, wenn Ihre Anwendung hardwarebeschleunigt ist. Eine Animation mit 60 Bildern pro Sekunde ist nicht immer möglich, wenn komplexe Ansichten animiert werden, die viele Zeichenvorgänge auslösen. Dies kann durch die Verwendung von Hardware-Layern zum Rendern der Ansicht in eine Hardware-Textur behoben werden. Die Hardware-Textur kann dann verwendet werden, um die Ansicht zu animieren. So muss die Ansicht nicht ständig neu gezeichnet werden, wenn sie animiert wird. Die Ansicht wird nur neu gezeichnet, wenn Sie die Attribute der Ansicht ändern, wodurch invalidate aufgerufen wird, oder wenn Sie invalidate manuell aufrufen. Wenn Sie in Ihrer Anwendung eine Animation ausführen und nicht die gewünschten flüssigen Ergebnisse erzielen, sollten Sie Hardware-Ebenen für die animierten Ansichten aktivieren.

Wenn eine Ansicht von einer Hardwareebene unterstützt wird, werden einige ihrer Eigenschaften durch die Art und Weise verarbeitet, wie die Ebene auf dem Bildschirm zusammengesetzt wird. Das Festlegen dieser Eigenschaften ist effizient, da die Ansicht nicht ungültig gemacht und neu gezeichnet werden muss. Im Folgenden finden Sie eine Liste der Eigenschaften, die sich auf die Art und Weise auswirken, wie die Ebene zusammengesetzt wird. Wenn Sie den Setter für eine dieser Eigenschaften aufrufen, wird die Zielansicht optimal ungültig gemacht und nicht neu gezeichnet:

  • alpha: Ändert die Deckkraft der Ebene.

  • x, y, translationX, translationY: Ändert die Position der Ebene.

  • scaleX, scaleY: Ändert die Größe der Ebene.

  • rotation, rotationX, rotationY: Ändert die Ausrichtung der Ebene im 3D-Raum.

  • pivotX, pivotY: Ändert den Ursprung der Transformationen der Ebene.

Diese Properties sind die Namen, die beim Animieren einer Ansicht mit einem ObjectAnimator verwendet werden. Wenn Sie auf diese Attribute zugreifen möchten, rufen Sie den entsprechenden Setter oder Getter auf. Wenn Sie beispielsweise die Alpha-Eigenschaft ändern möchten, rufen Sie setAlpha auf. Das folgende Code-Snippet zeigt, wie Sie eine 3D-Ansicht am effizientesten um die Y-Achse drehen:

Kotlin

view.setLayerType(View.LAYER_TYPE_HARDWARE, null)
ObjectAnimator.ofFloat(view, "rotationY", 180f).start()

Java

view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator.ofFloat(view, "rotationY", 180).start();

Da Hardware-Ebenen Videospeicher belegen, wird dringend empfohlen, sie nur für die Dauer der Animation zu aktivieren und danach wieder zu deaktivieren. Dies lässt sich mit Animations-Listenern erreichen:

Kotlin

view.setLayerType(View.LAYER_TYPE_HARDWARE, null)
ObjectAnimator.ofFloat(view, "rotationY", 180f).apply {
    addListener(object : AnimatorListenerAdapter() {
        override fun onAnimationEnd(animation: Animator) {
            view.setLayerType(View.LAYER_TYPE_NONE, null)
        }
    })
    start()
}

Java

view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "rotationY", 180);
animator.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        view.setLayerType(View.LAYER_TYPE_NONE, null);
    }
});
animator.start();

Weitere Informationen zur Attributanimation finden Sie unter Attributanimation.

Tipps und Tricks

Durch die Umstellung auf hardwarebeschleunigte 2D-Grafiken kann die Leistung sofort gesteigert werden. Sie sollten Ihre Anwendung jedoch so gestalten, dass die GPU effektiv genutzt wird. Beachten Sie dazu die folgenden Empfehlungen:

Anzahl der Ansichten in Ihrer Anwendung reduzieren
Je mehr Ansichten das System rendern muss, desto langsamer ist es. Das gilt auch für die Software-Rendering-Pipeline. Das Reduzieren von Ansichten ist eine der einfachsten Möglichkeiten, die Benutzeroberfläche zu optimieren.
Overdraw vermeiden
Zeichnen Sie nicht zu viele Ebenen übereinander. Entfernen Sie alle Ansichten, die vollständig von anderen undurchsichtigen Ansichten darüber verdeckt werden. Wenn Sie mehrere Ebenen übereinander zeichnen müssen, sollten Sie sie in einer Ebene zusammenführen. Als Faustregel gilt, dass bei aktueller Hardware pro Frame nicht mehr als 2,5-mal so viele Pixel gezeichnet werden sollten, wie auf dem Bildschirm angezeigt werden (transparente Pixel in einem Bitmap zählen!).
Keine Renderobjekte in Zeichenmethoden erstellen
Ein häufiger Fehler ist, dass jedes Mal, wenn eine Rendering-Methode aufgerufen wird, ein neues Paint oder ein neues Path erstellt wird. Dadurch wird der Garbage Collector gezwungen, häufiger ausgeführt zu werden, und es werden Caches und Optimierungen in der Hardware-Pipeline umgangen.
Formen nicht zu oft ändern
Komplexe Formen, Pfade und Kreise werden beispielsweise mit Texturmasken gerendert. Jedes Mal, wenn Sie einen Pfad erstellen oder ändern, wird in der Hardware-Pipeline eine neue Maske erstellt, was mitunter sehr aufwendig sein kann.
Bitmaps nicht zu oft ändern
Jedes Mal, wenn Sie den Inhalt einer Bitmap ändern, wird sie beim nächsten Zeichnen wieder als GPU-Textur hochgeladen.
Alphaversion mit Vorsicht verwenden
Wenn Sie eine Ansicht mit setAlpha, AlphaAnimation oder ObjectAnimator transparent machen, wird sie in einem Off-Screen-Puffer gerendert, wodurch sich die erforderliche Fillrate verdoppelt. Wenn Sie den Alphawert auf sehr große Ansichten anwenden, sollten Sie den Layer-Typ der Ansicht auf LAYER_TYPE_HARDWARE festlegen.