Akceleracja sprzętowa

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:

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:

  1. Unieważnij hierarchię
  2. 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:

  1. Unieważnij hierarchię
  2. Nagrywanie i aktualizowanie list wyświetlanych
  3. 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 jak LAYER_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 elementu ColorMatrixColorFilter.
  • 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ść warstwy
  • x, y, translationX, translationY: zmienia pozycję warstwy
  • scaleX, scaleY: zmienia rozmiar warstwy
  • rotation, rotationX, rotationY: zmienia orientację warstwy w przestrzeni 3D
  • pivotX, 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 nowego Path 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 lub ObjectAnimator, 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 na LAYER_TYPE_HARDWARE.