Potok renderowania 2D w Androidzie obsługuje akcelerację sprzętową, co oznacza, że wszystkie operacje rysowania wykonywane na obiekcie canvas korzystają z GPU. Ze względu na zwiększone zasoby wymagane do włączenia akceleracji sprzętowej aplikacja będzie zużywać więcej pamięci RAM.
Akceleracja sprzętowa jest domyślnie włączona. Jeśli Twoja aplikacja korzysta tylko ze standardowych funkcji kompozycyjnych, włączenie tej opcji globalnie nie powinno powodować żadnych niekorzystnych efektów rysowania. Jednak przyspieszenie sprzętowe nie jest obsługiwane w przypadku wszystkich operacji rysowania 2D, więc włączenie tej funkcji może wpłynąć na niektóre niestandardowe wywołania rysowania. Problemy zwykle objawiają się jako niewidoczne elementy, wyjątki lub nieprawidłowo renderowane piksele. Aby temu zapobiec, Android umożliwia włączanie i wyłączanie akceleracji sprzętowej na różnych poziomach. Zobacz Kontrolowanie akceleracji sprzętowej.
Jeśli aplikacja wykonuje niestandardowe rysowanie, przetestuj ją na rzeczywistych urządzeniach z włączoną akceleracją sprzętową, aby wykryć ewentualne problemy. W sekcji Obsługa operacji rysowania opisujemy znane problemy z akceleracją sprzętową i sposoby ich obejścia.
Zobacz też OpenGL z interfejsami Framework API.
Zarządzanie akceleracją sprzętową
Akcelerację sprzętową możesz kontrolować na tych poziomach:
- Aplikacja
- Aktywność
- Okno
- Kompozycyjne
Poziom aplikacji
Aby włączyć akcelerację sprzętową w całej aplikacji, dodaj ten atrybut do tagu <application> w pliku manifestu Androida:
<application android:hardwareAccelerated="true" ...>
Poziom aktywności
Jeśli aplikacja nie działa prawidłowo przy włączonym globalnie przyspieszeniu sprzętowym, możesz sterować nim także w przypadku poszczególnych aktywności. Aby włączyć lub wyłączyć akcelerację sprzętową na poziomie aktywności, możesz użyć atrybutu android:hardwareAccelerated dla elementu <activity>. W tym przykładzie akceleracja sprzętowa jest włączona w całej aplikacji, ale wyłączona w jednym działaniu:
<application android:hardwareAccelerated="true">
<activity ... />
<activity android:hardwareAccelerated="false" />
</application>
Poziom okna
Jeśli potrzebujesz jeszcze większej kontroli, możesz włączyć akcelerację sprzętową w danym oknie za pomocą tego kodu:
window.setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
)
Poziom kompozycyjny
W Compose nie ma przełącznika, który umożliwiałby wyłączenie akceleracji sprzętowej w przypadku poszczególnych funkcji kompozycyjnych.
Aby wyrenderować komponent w osobnej warstwie, użyj Modifier.graphicsLayer. Dzięki temu właściwości przekształcenia (takie jak alpha, scaleX, scaleY, translationX, translationY, rotationX, rotationY, rotationZ i transformOrigin) mogą się zmieniać bez ponownego uruchamiania kodu rysowania funkcji kompozycyjnej. Aby uzyskać najlepszą wydajność, do ustawiania tych właściwości zawsze używaj formy lambda modyfikatora.
Aby jawnie wymusić bufor poza ekranem w przypadku zaawansowanych operacji rysowania, takich jak niestandardowe mieszanie w warstwie, użyj CompositingStrategy.Offscreen.
Więcej informacji znajdziesz w sekcji Modyfikatory grafiki.
Jeśli masz niestandardową operację rysowania, która wymaga renderowania programowego, możesz hostować starszy widok za pomocą AndroidView i wywoływać setLayerType(View.LAYER_TYPE_SOFTWARE, null) w tym widoku.
Obsługa operacji rysowania
W przypadku akceleracji sprzętowej potok renderowania 2D obsługuje najczęściej używane operacje rysowania Canvas, a także wiele rzadziej używanych operacji. Obsługiwane są wszystkie operacje rysowania używane do renderowania aplikacji dostarczanych z Androidem, standardowych komponentów kompozycyjnych i popularnych zaawansowanych efektów wizualnych, takich jak odbicia i tekstury kafelkowe.
W tabeli poniżej znajdziesz opis poziomu obsługi różnych operacji na poszczególnych poziomach API:
| Pierwszy obsługiwany poziom interfejsu API | ||||
| Canvas | ||||
| drawBitmapMesh() (tablica kolorów) | 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() z obrotem lub perspektywą | 18 | |||
| Farba | ||||
| setAntiAlias() (w przypadku tekstu) | 18 | |||
| setAntiAlias() (w przypadku linii) | 16 | |||
| setFilterBitmap() | 17 | |||
| setLinearText() | ✗ | |||
| setMaskFilter() | ✗ | |||
| setPathEffect() (w przypadku linii) | 28 | |||
| setShadowLayer() (inny niż tekst) | 28 | |||
| setStrokeCap() (w przypadku linii) | 18 | |||
| setStrokeCap() (dla punktów) | 19 | |||
| setSubpixelText() | 28 | |||
| Xfermode | ||||
| PorterDuff.Mode.DARKEN (bufor ramki) | 28 | |||
| PorterDuff.Mode.LIGHTEN (bufor ramki) | 28 | |||
| PorterDuff.Mode.OVERLAY (bufor ramki) | 28 | |||
| Shader | ||||
| ComposeShader w ComposeShader | 28 | |||
| Shadery tego samego typu w funkcji ComposeShader | 28 | |||
| Macierz lokalna w funkcji ComposeShader | 18 | |||
Skalowanie obszaru roboczego
Potok renderowania 2D z akceleracją sprzętową został stworzony z myślą o obsłudze rysowania bez skalowania, przy czym niektóre operacje rysowania znacznie pogarszają jakość przy wyższych wartościach skali. Operacje te są realizowane jako tekstury rysowane w skali 1,0, przekształcane przez procesor graficzny. Od poziomu API 28 wszystkie operacje rysowania mogą być skalowane bez problemów.
W tabeli poniżej znajdziesz informacje o tym, kiedy wdrożono zmiany, aby prawidłowo obsługiwać duże skale:
| Operacja rysowania, która ma zostać przeskalowana. | Pierwszy obsługiwany poziom interfejsu API |
| drawText() | 18 |
| drawPosText() | 28 |
| drawTextOnPath() | 28 |
| Proste kształty | 17 |
| Złożone kształty | 28 |
| drawPath() | 28 |
| Warstwa cienia | 28 |
Jeśli operacja rysowania, od której zależy działanie aplikacji, nie jest akcelerowana sprzętowo, wyrenderuj ją w programowym buforze poza ekranem Bitmap (lub ImageBitmap) i narysuj wynik. Pozostała część interfejsu użytkownika zachowuje ścieżkę akceleracji sprzętowej.
Porady i wskazówki
Przełączenie się na akcelerowaną sprzętowo grafikę 2D może natychmiast zwiększyć wydajność, ale nadal warto zaprojektować aplikację tak, aby efektywnie korzystała z układu GPU. W tym celu postępuj zgodnie z tymi rekomendacjami:
- Zminimalizuj złożoność układu i rekompozycję
- Utrzymuj płytką strukturę układu i ograniczaj liczbę ponownych kompozycji. Odczytywanie stanu należy odraczać do najwęższego zakresu, aby zmiana powodowała ponowne rysowanie jak najmniejszego regionu. Na przykład odczytuj stan animacji w funkcji
Modifier.graphicsLayer { }, a nie w treści funkcji kompozycyjnej. Więcej informacji znajdziesz w artykule Wydajność Jetpack Compose. - Unikaj przekroczenia limitu
- Nie rysuj zbyt wielu warstw na sobie. Usuń wszystkie elementy interfejsu, które są całkowicie zasłonięte przez inne nieprzezroczyste elementy znajdujące się nad nimi. Jeśli musisz narysować kilka warstw nałożonych na siebie, rozważ połączenie ich w jedną warstwę. Dobrym rozwiązaniem w przypadku obecnego sprzętu jest nie rysować więcej niż 2,5 raza liczby pikseli na ekranie na klatkę (przezroczyste piksele w mapie bitowej też się liczą!).
- Nie twórz obiektów renderowania w metodach rysowania
- Częstym błędem jest tworzenie nowego obiektu
PaintlubPathza każdym razem, gdy wywoływana jest metoda renderowania. Wymusza to częstsze uruchamianie modułu odśmiecania pamięci, a także pomija pamięci podręczne i optymalizacje w potoku sprzętowym. Aby tego uniknąć, ponownie wykorzystuj i modyfikuj obiekty:- Używaj standardowych metod: standardowe metody
DrawScope(np.drawRectidrawCircle) wewnętrznie ponownie wykorzystują obiektyPaintbez konieczności przydzielania ich przez dewelopera. - Modyfikuj zamiast przydzielać ponownie: podczas pisania niestandardowej logiki używaj
path.rewind, aby wyczyścić istniejący elementPath, zamiast tworzyć nowy elementPath. - Efektywne przechowywanie stanu: w funkcji kompozycyjnej przydzielaj obiekty tylko raz za pomocą funkcji
remember { Path() }. Jeśli tworzysz rozszerzenia z ponownym użyciem niestandardowych modyfikatorów, zaimplementuj niestandardowy elementModifier.Nodeza pomocąDrawModifierNode, aby przydzielać i ponownie wykorzystywać obiekty bez powodowania nowych przydziałów pamięci sterty.
- Używaj standardowych metod: standardowe metody
- Nie modyfikuj kształtów zbyt często
- Złożone kształty, ścieżki i okręgi są renderowane przy użyciu masek tekstur. Za każdym razem, gdy tworzysz lub modyfikujesz ścieżkę, potok sprzętowy tworzy nową maskę, co może być kosztowne.
- Nie modyfikuj zbyt często bitmap
- Za każdym razem, gdy zmieniasz zawartość bitmapy, jest ona ponownie przesyłana jako tekstura GPU przy następnym rysowaniu.
- Używaj wersji alfa z rozwagą
- Gdy tworzysz komponent z przezroczystością za pomocą interfejsów API
Modifier.alphalub interfejsów API animacji Compose, jest on zwykle renderowany w buforze poza ekranem, co podwaja wymagany współczynnik wypełnienia. Aby uniknąć narzutu bufora poza ekranem w przypadku treści, które się nie nakładają, ustaw wartośćCompositingStrategy.ModulateAlpha. W przypadku poszczególnych wywołań rysowania zastosuj wartość alfa bezpośrednio do polecenia rysowania (tak jak w przypadkucolor = Color.Red.copy(alpha = 0.5f)) bez tworzenia warstwy.