Od Androida 3.0 (poziom interfejsu API 11) potok renderowania 2D w Androidzie obsługuje akcelerację sprzętową, co oznacza, że wszystkie operacje rysowania wykonywane w obszarze roboczym elementu View
korzystają z GPU. Ze względu na większe zasoby wymagane do włączenia akceleracji sprzętowej aplikacja zużywa więcej pamięci RAM.
Akceleracja sprzętowa jest domyślnie włączona, jeśli docelowy poziom interfejsu API wynosi >=14, ale można ją też włączyć jawnie. Jeśli Twoja aplikacja używa tylko standardowych widoków i widoków Drawable
, włączenie jej globalnie nie powinno spowodować negatywnego wpływu na proces rysowania. Jednak akceleracja sprzętowa nie jest obsługiwana w przypadku wszystkich operacji rysowania 2D, więc jej włączenie może mieć wpływ na niektóre widoki niestandardowe lub generować połączenia. Problemy zwykle wyglądają jak niewidoczne elementy, wyjątki lub nieprawidłowo renderowane piksele. Aby temu zapobiec, Android umożliwia włączanie i wyłączanie akceleracji sprzętowej na wielu poziomach. Zobacz Sterowanie akceleracją sprzętową.
Jeśli aplikacja wykonuje rysowanie niestandardowe, przetestuj ją na rzeczywistych urządzeniach z włączoną akceleracją sprzętową, aby znaleźć ewentualne problemy. Sekcja Obsługa operacji rysowania zawiera opis znanych problemów z akceleracją sprzętową oraz sposobów ich rozwiązywania.
Zobacz też OpenGL z interfejsami Framework API i Renderscript.
Steruj akceleracją sprzętową
Akcelerację sprzętową możesz sterować na tych poziomach:
- Zgłoszenie do programu
- Aktywność
- Okno
- Wyświetl
Poziom aplikacji
W pliku manifestu Androida dodaj ten atrybut do tagu
<application>
, aby włączyć akcelerację sprzętową dla całej aplikacji:
<application android:hardwareAccelerated="true" ...>
Poziom aktywności
Jeśli aplikacja nie działa prawidłowo przy włączonej globalnej akceleracji sprzętowej, możesz ją kontrolować również dla poszczególnych działań. Aby włączyć lub wyłączyć akcelerację sprzętową na poziomie aktywności, możesz użyć atrybutu android:hardwareAccelerated
elementu
<activity>
. Poniższy przykład włącza akcelerację sprzętową w całej aplikacji, ale wyłącza ją dla jednego działania:
<application android:hardwareAccelerated="true"> <activity ... /> <activity android:hardwareAccelerated="false" /> </application>
Poziom okna
Jeśli potrzebujesz jeszcze bardziej szczegółowej kontroli, możesz włączyć akcelerację sprzętową dla danego okna, używając tego kodu:
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);
Uwaga: obecnie nie można wyłączyć akceleracji sprzętowej na poziomie okna.
Poziom widoku
Możesz wyłączyć akcelerację sprzętową dla pojedynczego widoku w czasie działania, używając tego kodu:
Kotlin
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
Java
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
Uwaga: obecnie nie można włączyć akceleracji sprzętowej na poziomie widoku. Warstwy widoku mają też inne funkcje poza wyłączeniem akceleracji sprzętowej. Więcej informacji o zastosowaniu tych warstw znajdziesz w artykule Wyświetlanie warstw.
Sprawdzanie, czy widok jest z akceleracją sprzętową
Czasem przydaje się, aby aplikacja wiedziała, czy jest obecnie akcelerowana sprzętowo, zwłaszcza w przypadku takich elementów jak widoki niestandardowe. Jest to szczególnie przydatne, gdy Twoja aplikacja wykonuje wiele niestandardowych rysunków i nie wszystkie operacje są prawidłowo obsługiwane przez nowy potok renderowania.
Istnieją dwa sposoby sprawdzenia, czy aplikacja korzysta z akceleracji sprzętowej:
View.isHardwareAccelerated()
zwracatrue
, jeśliView
jest podłączony do okna z akceleracją sprzętową.Canvas.isHardwareAccelerated()
zwraca wartośćtrue
, jeśliCanvas
korzysta z akceleracji sprzętowej
Jeśli musisz to sprawdzić w kodzie rysunku, w miarę możliwości używaj Canvas.isHardwareAccelerated()
zamiast View.isHardwareAccelerated()
. Po dołączeniu widoku do okna z akceleracją sprzętową można go nadal rysować za pomocą akceleracji sprzętowej Canvas. Dzieje się tak na przykład podczas rysowania widoku do bitmapy w celu buforowania.
Modele rysunkowe na Androida
Gdy akceleracja sprzętowa jest włączona, platforma Android używa nowego modelu rysowania, który wykorzystuje listy wyświetlania do renderowania aplikacji na ekranie. Aby w pełni zrozumieć listy wyświetlane i ich wpływ na aplikację, warto dowiedzieć się, jak Android generuje widoki bez akceleracji sprzętowej. W kolejnych sekcjach opisujemy modele rysowania oparte na oprogramowaniu i akceleracji sprzętowej.
Programowy model rysunkowy
W modelu rysunkowym oprogramowania widoki są rysowane w następujący sposób:
- Unieważnij hierarchię
- Narysuj hierarchię
Za każdym razem, gdy aplikacja musi zaktualizować część swojego interfejsu, wywołuje invalidate()
(lub jeden z jej wariantów) w każdym widoku, w którym zmieniła się treść. Komunikaty o unieważnieniu są rozpowszechniane aż do poziomu hierarchii widoków, tak aby możliwe było przerysowanie obszarów ekranu (czyli brudnego obszaru). System Android rysuje następnie dowolny widok w hierarchii, który przecina się z brudnym regionem. Taki model ma jednak dwie wady:
- Po pierwsze, ten model wymaga wykonania dużej ilości kodu przy każdym przebiegu rysowania. Jeśli na przykład aplikacja wywołuje funkcję
invalidate()
na przycisku, który znajduje się nad innym widokiem, system Android ponownie rysuje widok, mimo że ten sam się nie zmienił. - Drugim problemem jest to, że model rysunkowy może ukrywać błędy w aplikacji. System Android ponownie rysuje widoki, gdy przecinają one brudny region, dlatego widok, którego treść została przez Ciebie zmieniona, może zostać ponownie wyświetlony, mimo że
invalidate()
nie został do niego wywołany. W takim przypadku inny widok danych zostanie unieważniony, aby zapewnić jego prawidłowe działanie. To zachowanie może się zmieniać za każdym razem, gdy zmodyfikujesz aplikację. Z tego powodu zawsze należy wywoływać w widokach niestandardowych metodęinvalidate()
za każdym razem, gdy zmieniasz dane lub stan, który wpływa na kod rysowania widoku.
Uwaga: widoki Androida automatycznie wywołują polecenie invalidate()
, gdy zmienią się ich właściwości, np. kolor tła lub tekst w elemencie TextView
.
Model rysowania z akceleracją sprzętową
System Android nadal używa invalidate()
i draw()
do wysyłania próśb o aktualizację ekranu i renderowania widoków, ale rysowanie wygląda inaczej. Zamiast od razu wykonywać polecenia rysowania, system Android rejestruje je na listach wyświetlania, które zawierają dane wyjściowe kodu rysowania hierarchii widoków. Kolejna optymalizacja polega na tym, że system Android musi rejestrować i aktualizować listy wyświetlane tylko w przypadku widoków oznaczonych za pomocą wywołania invalidate()
. Wyświetlenia, które nie zostały unieważnione, można przywrócić, ponownie wystawiając wcześniej zarejestrowaną listę wyświetleń. Nowy model rysowania składa się z 3 etapów:
- Unieważnij hierarchię
- Nagrywanie i aktualizowanie list wyświetlanych
- Narysuj wyświetlane listy
W tym modelu nie można polegać na tym, że widok przecina obszar brudny, aby została wykonana jej metoda draw()
. Aby mieć pewność, że system Android zarejestruje listę wyświetlania widoku, musisz wywołać invalidate()
. Jeśli o tym zapominasz, widok wygląda tak samo nawet po wprowadzeniu zmian.
Korzystanie z list wyświetlania poprawia również wydajność animacji, ponieważ określenie określonych właściwości, takich jak alfa czy obrót, nie wymaga unieważnienia widoku docelowego (dzieje się to automatycznie). Optymalizacja ta odnosi się również do widoków z listą wyświetlania (w dowolnym widoku, w którym aplikacja jest z akceleracją sprzętową). Załóżmy np., że istnieje LinearLayout
, który zawiera wartość ListView
nad kolumną Button
. Lista wyświetlana dla elementu LinearLayout
wygląda tak:
- DrawDisplayList(ListView)
- DrawDisplayList(przycisk)
Załóżmy, że chcesz zmienić przezroczystość elementu ListView
. Po wywołaniu funkcji setAlpha(0.5f)
w ListView
lista wyświetlania zawiera teraz:
- SaveLayerAlpha(0,5)
- DrawDisplayList(ListView)
- Przywróć
- DrawDisplayList(przycisk)
Złożony kod rysunkowy ListView
nie został wykonany. System zaktualizował tylko listę wyświetlaną w postaci znacznie prostszej funkcji LinearLayout
. W aplikacji bez włączonej akceleracji sprzętowej kod rysunkowy zarówno listy, jak i jej elementu nadrzędnego jest wykonywany ponownie.
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 z Androidem, domyślne widżety i układy oraz popularne zaawansowane efekty wizualne, takie jak odbicia i tekstury ułożone obok siebie.
W tabeli poniżej znajdziesz opis poziomu obsługi różnych operacji na różnych poziomach interfejsu API:
Pierwszy obsługiwany poziom interfejsu API | ||||
Canvas | ||||
DrawBitmapMesh() (tablica kolorów) | 18 | |||
Rysuj_obraz(y) | 23 | |||
DrawPosText() | 16 | |||
DragTextOnPath() | 16 | |||
panelVertices() | 29 | |||
setDrawFilter() | 16 | |||
clipPath() | 18 | |||
clipRegion() | 18 | |||
clipRect(Region.Op.XOR) | 18 | |||
clipRect(Region.Op.Różnica) | 18 | |||
clipRect(Region.Op.ReverseDifference) | 18 | |||
clipRect() z obrotem/perspektywą | 18 | |||
Barwiony | ||||
setAntiAlias() (dla tekstu) | 18 | |||
setAntiAlias() (dla wierszy) | 16 | |||
setFilterBitmap() | 17 | |||
setLinearText() | ✗ | |||
setMaskFilter() | ✗ | |||
setPathEffect() (dla wierszy) | 28 | |||
setShadowLayer() (inny niż tekst) | 28 | |||
setStrokeCap() (dla wierszy) | 18 | |||
setStrokeCap() (dla punktów) | 19 | |||
setSubpixelText() | 28 | |||
Xfermode | ||||
PorterDuff.Mode.DARKEN (framebuffer) | 28 | |||
PorterDuff.Mode.lightEN (Framebuffer) | 28 | |||
PorterDuff.Mode.OVERLAY (bufor ramki) | 28 | |||
Shaker | ||||
ComposeShader w elemencie ComposeShader | 28 | |||
Shadery tego samego typu w ComposeShader | 28 | |||
Lokalna macierz w ComposeShader | 18 |
Skalowanie obszaru roboczego
Potok renderowania 2D z akceleracją sprzętową został utworzony jako pierwszy z myślą o obsłudze nieskalowanego rysowania, przy czym niektóre operacje rysowania obniżają jakość znacznie przy dużych wartościach. Operacje te są implementowane jako tekstury rysowane w skali 1, 0 i przekształcane przez GPU. Od poziomu interfejsu API 28 wszystkie operacje rysowania mogą być skalowane bez problemów.
W tabeli poniżej znajdziesz informacje o tym, kiedy implementacja została zmieniona, aby prawidłowo obsługiwała dużą skalę:Operacja rysowania na potrzeby skalowania | Pierwszy obsługiwany poziom interfejsu API |
DrewText() | 18 |
DrawPosText() | 28 |
DragTextOnPath() | 28 |
Proste kształty* | 17 |
Złożone kształty* | 28 |
DrewPath() | 28 |
Warstwa cieni | 28 |
Uwaga: „proste” kształty to polecenia drawRect()
, drawCircle()
, drawOval()
, drawRoundRect()
i drawArc()
(z useCenter=false) wydawane za pomocą funkcji Paint, która nie zawiera elementu PathEffect oraz nie zawiera złączenia innych niż domyślne (za pomocą setStrokeJoin()
lub setStrokeMiter()
). Inne wystąpienia tych poleceń rysowania mają na tym wykresie oznaczenie „Złożone”.
Jeśli któryś z brakujących funkcji lub ograniczeń ma wpływ na Twoją aplikację, możesz wyłączyć akcelerację sprzętową tylko dla danej części aplikacji, której dotyczy problem, wywołując setLayerType(View.LAYER_TYPE_SOFTWARE, null)
. Dzięki temu nadal możesz korzystać z akceleracji sprzętowej w innych miejscach. Więcej informacji o włączaniu i wyłączaniu akceleracji sprzętowej na różnych poziomach w aplikacji znajdziesz w artykule Sterowanie akceleracją sprzętową.
Wyświetl warstwy
We wszystkich wersjach Androida widoki mogły renderować się w buforach poza ekranem: z użyciem pamięci podręcznej rysunków w widoku lub Canvas.saveLayer()
. Bufory (czyli warstwy) poza ekranem mają różne zastosowania. Możesz ich używać do animowania złożonych widoków lub zastosowania efektów kompozycji, by zwiększyć skuteczność. Możesz na przykład wdrożyć efekty rozmycia za pomocą Canvas.saveLayer()
, aby tymczasowo renderować widok w warstwę, a następnie skomponować go z powrotem na ekran i zastosować współczynnik przezroczystości.
Od Androida 3.0 (poziom interfejsu API 11) masz większą kontrolę nad tym, jak i kiedy używać warstw za pomocą metody View.setLayerType()
. Ten interfejs API przyjmuje 2 parametry: typ warstwy, której chcesz użyć, i opcjonalny obiekt Paint
opisujący sposób skomponowania warstwy. Za pomocą parametru Paint
możesz stosować do warstwy filtry kolorów, specjalne tryby mieszania lub nieprzezroczystość. Widok może korzystać z jednego z trzech typów warstw:
LAYER_TYPE_NONE
: widok jest renderowany normalnie i nie jest obsługiwany przez bufor poza ekranem. Jest to jego ustawienie domyślne.LAYER_TYPE_HARDWARE
: widok jest renderowany sprzętowo w postaci tekstury sprzętowej, jeśli aplikacja jest z akceleracją sprzętową. Jeśli aplikacja nie jest z akceleracją sprzętową, ten typ warstwy działa tak samo jakLAYER_TYPE_SOFTWARE
.LAYER_TYPE_SOFTWARE
: widok jest renderowany przez oprogramowanie w postaci bitmapy.
Typ używanej warstwy zależy od Twojego celu:
- Wydajność: użyj warstwy sprzętowej, aby renderować widok w teksturę sprzętową. Po wyrenderowaniu widoku w warstwę jego kod rysowania nie musi być wykonany, dopóki widok nie wywoła funkcji
invalidate()
. Niektóre animacje, np. animacje alfa, można stosować bezpośrednio w warstwie, co jest bardzo skuteczne w przypadku GPU. - Efekty wizualne: za pomocą typu warstwy sprzętowej lub programowej oraz
Paint
możesz stosować specjalne efekty wizualne. Możesz na przykład narysować widok czarno-biały przy użyciu elementuColorMatrixColorFilter
. - Zgodność: użyj typu warstwy oprogramowania, aby wymuszać renderowanie widoku w oprogramowaniu. Jeśli widok z akceleracją sprzętową (np. cała aplikacja jest akredytowana sprzętowo) ma problemy z renderowaniem, to prosty sposób na obejście ograniczeń sprzętowego potoku renderowania.
Wyświetlanie warstw i animacji
Warstwy sprzętowe mogą wyświetlać szybsze i płynniejsze animacje, gdy aplikacja jest z akceleracją sprzętową. W przypadku animacji złożonych widoków, które wymagają wykonania wielu operacji rysowania, nie zawsze można uruchomić animację z szybkością 60 klatek na sekundę. Można go złagodzić, używając warstw sprzętowych do renderowania widoku na teksturę sprzętową. Można jej użyć do animowania widoku, eliminując potrzebę ciągłego rysowania go podczas animacji. Widok nie zostanie ponownie rysowany, chyba że zmienisz jego właściwości, które wywołują invalidate()
, lub ręcznie wywołasz metodę invalidate()
. Jeśli wykonujesz animację w aplikacji i nie uzyskujesz oczekiwanych efektów, rozważ włączenie warstw sprzętowych w widokach animowanych.
Jeśli widok jest oparty na warstwie sprzętowej, niektóre jego właściwości są obsługiwane w taki sposób, w jaki warstwa warstwowa na ekranie. Ustawienie tych właściwości będzie skuteczne, ponieważ nie wymagają unieważniania i ponownego rysowania widoku. Poniższa lista właściwości wpływa na sposób komponowania warstwy. Wywołanie metody ustawiającej dla dowolnej z tych właściwości powoduje optymalne unieważnienie widoku i nie jest modyfikowane w widoku docelowym:
alpha
: zmienia przezroczystość warstwyx
,y
,translationX
,translationY
: zmienia pozycję warstwyscaleX
,scaleY
: zmienia rozmiar warstwyrotation
,rotationX
,rotationY
: zmienia orientację warstwy w przestrzeni 3DpivotX
,pivotY
: zmienia pochodzenie przekształceń warstwy
Te właściwości są nazwami używanymi podczas animowania widoku za pomocą funkcji ObjectAnimator
. Aby uzyskać dostęp do tych właściwości, wywołaj odpowiednią metodę ustawiania lub pobierania. Aby np. zmodyfikować właściwość w wersji alfa, wywołaj setAlpha()
. Poniższy fragment kodu przedstawia najskuteczniejszy sposób obracania widoku w 3D wokół osi Y:
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();
Warstwy sprzętowe zajmują pamięć wideo, dlatego zdecydowanie zalecamy ich włączanie tylko na czas trwania animacji, a potem wyłączanie ich po zakończeniu animacji. Możesz to zrobić, używając detektorów animacji:
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();
Więcej informacji o animacji właściwości znajdziesz w artykule Animacja właściwości.
Porady i wskazówki
Przejście na grafikę 2D z akceleracją sprzętową może natychmiast zwiększyć wydajność, ale nadal warto zaprojektować aplikację tak, aby efektywnie wykorzystywała GPU. W tym celu postępuj zgodnie z tymi zaleceniami:
- Zmniejszanie liczby wyświetleń w aplikacji
- Im więcej widoków musi wyświetlić system, tym działa to wolniej. Dotyczy to również potoku renderowania oprogramowania. Zmniejszanie liczby wyświetleń to jeden z najprostszych sposobów optymalizacji interfejsu użytkownika.
- Unikaj przerysowywania
- Nie rysuj zbyt wielu warstw na sobie. Usuń wszystkie widoki, które są całkowicie zasłonięte innymi nieprzezroczystymi widokami. Jeśli chcesz narysować kilka warstw połączonych ze sobą, rozważ połączenie ich w jedną warstwę. Przy obecnym sprzęcie należy stosować zasadę, aby nie rysować więcej niż 2,5 raza więcej pikseli na ekranie na klatkę (liczba przezroczystych pikseli w bitmacie).
- Nie twórz obiektów renderowanych w metodach rysowania
- Częstym błędem jest tworzenie nowego
Paint
lub nowegoPath
przy każdym wywołaniu metody renderowania. Wymusza to częstsze działanie kolektora śmieci, a także pomija pamięć podręczną i optymalizacje w potoku sprzętowym. - Nie zmieniaj kształtów zbyt często
- Na przykład złożone kształty, ścieżki i okręgi są renderowane za pomocą masek tekstur. Za każdym razem, gdy tworzysz lub zmieniasz ścieżkę, potok sprzętowy tworzy nową maskę, co może być kosztowne.
- Nie modyfikuj map bitowych zbyt często
- Za każdym razem, gdy zmienisz zawartość bitmapy, przy jej następnym rysowaniu jest ona przesyłana ponownie jako tekstura GPU.
- Ostrożnie używaj wersji alfa
- Jeśli ustawisz półprzezroczysty widok za pomocą elementów
setAlpha()
,AlphaAnimation
lubObjectAnimator
, będzie on renderowany w buforze poza ekranem, który podwaja wymagany współczynnik wypełnienia. Jeśli stosujesz wersję alfa do bardzo dużych widoków, rozważ ustawienie typu warstwy widoku naLAYER_TYPE_HARDWARE
.