Die Android-2D-Rendering-Pipeline unterstützt die Hardwarebeschleunigung. Das bedeutet, dass alle Zeichenvorgänge, die auf dem Canvas ausgeführt werden, die GPU verwenden. Aufgrund der erhöhten Ressourcen, die für die Hardwarebeschleunigung erforderlich sind, verbraucht Ihre App mehr RAM.
Die Hardwarebeschleunigung ist standardmäßig aktiviert. Wenn Ihre Anwendung nur Standard-Composables verwendet, sollte das globale Aktivieren keine negativen Auswirkungen auf das Rendern haben. Da die Hardwarebeschleunigung jedoch nicht für alle 2D-Zeichnungsvorgänge unterstützt wird, kann sich die Aktivierung auf einige Ihrer benutzerdefinierten Zeichenaufrufe auswirken. Probleme äußern sich in der Regel als unsichtbare Elemente, Ausnahmen oder falsch gerenderte Pixel. Um dieses Problem zu beheben, bietet Android Ihnen 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.
Weitere Informationen finden Sie unter OpenGL mit den Framework-APIs.
Hardwarebeschleunigung steuern
Sie können die Hardwarebeschleunigung auf den folgenden Ebenen steuern:
- Anwendung
- Aktivität
- Fenster
- Zusammensetzbar
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:
window.setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
)
Zusammenfassungsstufe
In Compose gibt es keinen Schalter pro Composable, um die Hardwarebeschleunigung zu deaktivieren.
Wenn Sie ein Composable in einer eigenen Ebene rendern möchten, verwenden Sie Modifier.graphicsLayer. So können sich Transformationsattribute (z. B. alpha, scaleX, scaleY, translationX, translationY, rotationX, rotationY, rotationZ und transformOrigin) ändern, ohne dass der Zeichencode des Composables noch einmal ausgeführt werden muss. Für die beste Leistung sollten Sie immer die Lambda-Form des Modifikators verwenden, um diese Eigenschaften festzulegen.
Wenn Sie explizit einen Offscreen-Puffer für erweiterte Zeichenvorgänge wie benutzerdefiniertes Blending innerhalb der Ebene erzwingen möchten, verwenden Sie CompositingStrategy.Offscreen.
Weitere Informationen finden Sie unter Grafikmodifikatoren.
Wenn Sie einen benutzerdefinierten Zeichenvorgang haben, der unbedingt Software-Rendering erfordert, können Sie eine Legacy-Ansicht mit AndroidView hosten und setLayerType(View.LAYER_TYPE_SOFTWARE, null) für diese Ansicht aufrufen.
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-Composables und 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 eine Zeichenoperation, von der Sie abhängen, nicht hardwarebeschleunigt ist, rendern Sie die betroffene Zeichnung in einem Off-Screen-Software-Bitmap (oder ImageBitmap) und zeichnen Sie das Ergebnis. Der Rest der Benutzeroberfläche behält den hardwarebeschleunigten Pfad bei.
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:
- Layoutkomplexität und Neuzusammenstellung minimieren
- Halten Sie den Layoutbaum flach und begrenzen Sie die Anzahl der Neuzusammenstellungen. Verschieben Sie das Lesen des Status auf den engsten Bereich, damit bei einer Änderung die kleinstmögliche Region neu gezeichnet wird. Lesen Sie beispielsweise den animierten Status innerhalb von
Modifier.graphicsLayer { }anstatt im Hauptteil einer Composable-Funktion. Weitere Informationen finden Sie unter Jetpack Compose-Leistung. - Overdraw vermeiden
- Zeichnen Sie nicht zu viele Ebenen übereinander. Entfernen Sie alle UI-Elemente, die vollständig von anderen undurchsichtigen Elementen 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
Paintoder ein neuesPatherstellt wird. Dadurch wird der Garbage Collector gezwungen, häufiger ausgeführt zu werden, und es werden auch Caches und Optimierungen in der Hardware-Pipeline umgangen. Um dies zu vermeiden, sollten Sie Ihre Objekte wiederverwenden und ändern:- Standardmethoden verwenden: Bei Standardmethoden für
DrawScope(z. B.drawRectunddrawCircle) werdenPaint-Objekte intern wiederverwendet, ohne dass eine Zuweisung durch den Entwickler erforderlich ist. - Mutieren statt neu zuzuweisen: Wenn Sie benutzerdefinierte Logik schreiben, verwenden Sie
path.rewind, um ein vorhandenesPathzu löschen, anstatt ein neuesPathzu instanziieren. - Zustand effizient beibehalten: Weisen Sie Objekte in einem Composable einmal mit
remember { Path() }zu. Wenn Sie wiederverwendbare benutzerdefinierte Modifikatorerweiterungen erstellen, implementieren Sie eine benutzerdefinierteModifier.NodemitDrawModifierNode, um die Objekte zuzuweisen und wiederzuverwenden, ohne neue Heap-Zuweisungen zu verursachen.
- Standardmethoden verwenden: Bei Standardmethoden für
- 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 mit
Modifier.alphaoder Compose-Animations-APIs eine zusammensetzbare, durchscheinende Ebene erstellen, wird sie in der Regel in einem Off-Screen-Puffer gerendert, wodurch sich die erforderliche Fillrate verdoppelt. Um den Overhead des Off-Screen-Puffers für nicht überlappende Inhalte zu vermeiden, legen SieCompositingStrategy.ModulateAlphafest. Bei einzelnen Zeichenaufrufen wenden Sie den Alphawert direkt auf den Zeichenbefehl an (wie beicolor = Color.Red.copy(alpha = 0.5f)), ohne eine Ebene zu erstellen.