Pojęcia i implementacja w Jetpack Compose
Począwszy od Androida 3.0 (interfejs API na poziomie 11), potok renderowania 2D w Androidzie obsługuje akcelerację sprzętową, co oznacza, że wszystkie operacje rysowania wykonywane na płótnie View używają 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 docelowy poziom interfejsu API jest >=14, ale można ją też włączyć ręcznie. Jeśli Twoja aplikacja używa tylko standardowych widoków i Drawable, włączenie tej funkcji w całej aplikacji 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 widoki lub 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 wielu poziomach. Zobacz Sterowanie akceleracją sprzętową.
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 API platformy i Renderscript.
Zarządzanie akceleracją sprzętową
Akcelerację sprzętową możesz kontrolować na tych poziomach:
- Aplikacja
- Aktywność
- Okno
- Wyświetl
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:
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);
Poziom widoku
Możesz wyłączyć akcelerację sprzętową dla poszczególnych widoków w czasie działania programu za pomocą tego kodu:
Kotlin
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
Java
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
Sprawdzanie, czy widok jest akcelerowany sprzętowo
Czasami aplikacja potrzebuje informacji o tym, czy jest obecnie akcelerowana sprzętowo, zwłaszcza w przypadku elementów takich jak widoki niestandardowe. Jest to szczególnie przydatne, jeśli aplikacja wykonuje wiele niestandardowych operacji rysowania, a nie wszystkie z nich są prawidłowo obsługiwane przez nowy potok renderowania.
Istnieją 2 sposoby sprawdzenia, czy aplikacja korzysta z akceleracji sprzętowej:
View.isHardwareAcceleratedzwraca wartośćtrue, jeśli elementViewjest dołączony do okna z akceleracją sprzętową.Canvas.isHardwareAcceleratedzwraca wartośćtrue, jeśliCanvasjest akcelerowany sprzętowo.
Jeśli musisz przeprowadzić to sprawdzenie w kodzie rysowania, w miarę możliwości używaj Canvas.isHardwareAccelerated zamiast View.isHardwareAccelerated. Gdy widok jest dołączony do okna z akceleracją sprzętową, nadal można go rysować za pomocą obiektu Canvas bez akceleracji sprzętowej. Dzieje się tak na przykład podczas rysowania widoku na mapie bitowej w celu buforowania.
Modele rysowania na Androidzie
Gdy akceleracja sprzętowa jest włączona, platforma Androida korzysta z nowego modelu rysowania, który używa list wyświetlania do renderowania aplikacji na ekranie. Aby w pełni zrozumieć listy wyświetlania i ich wpływ na aplikację, warto też wiedzieć, jak Android rysuje widoki bez akceleracji sprzętowej. W sekcjach poniżej opisujemy modele rysowania oparte na oprogramowaniu i przyspieszane sprzętowo.
Model rysowania oparty na oprogramowaniu
W modelu rysowania oprogramowania widoki są rysowane w 2 krokach:
- Unieważnianie hierarchii
- Narysuj hierarchię
Gdy aplikacja musi zaktualizować część interfejsu, wywołuje metodę
invalidate() (lub jedną z jej odmian) w dowolnym widoku, którego treść uległa zmianie. Komunikaty o unieważnieniu są propagowane w górę hierarchii widoków, aby obliczyć regiony ekranu, które wymagają ponownego narysowania (tzw. regiony nieaktualne). System Android rysuje następnie wszystkie widoki w hierarchii, które przecinają obszar wymagający odświeżenia. Ten model rysowania ma jednak 2 wady:
Po pierwsze, ten model wymaga wykonania dużej ilości kodu przy każdym przejściu rysowania. Jeśli na przykład aplikacja wywołuje
invalidatena przycisku, a ten przycisk znajduje się na innym widoku, system Android ponownie rysuje widok, nawet jeśli nie uległ on zmianie.Drugi problem polega na tym, że model rysowania może ukrywać błędy w aplikacji. Ponieważ system Android ponownie rysuje widoki, gdy przecinają one obszar wymagający odświeżenia, widok, którego zawartość została zmieniona, może zostać ponownie narysowany, nawet jeśli nie wywołano na nim metody
invalidate. W takim przypadku polegasz na unieważnieniu innego widoku, aby uzyskać prawidłowe działanie. To zachowanie może się zmieniać za każdym razem, gdy modyfikujesz aplikację. Dlatego w przypadku widoków niestandardowych zawsze wywołuj metodęinvalidate, gdy modyfikujesz dane lub stan, które mają wpływ na kod rysowania widoku.
Model rysowania z akceleracją sprzętową
System Android nadal używa metod invalidate i draw do wysyłania żądań aktualizacji ekranu i renderowania widoków, ale samo rysowanie odbywa się inaczej.
Zamiast od razu wykonywać polecenia rysowania, system Android zapisuje je na listach wyświetlania, które zawierają dane wyjściowe kodu rysowania hierarchii widoków. Kolejną optymalizacją jest to, że system Android musi rejestrować i aktualizować listy wyświetlania tylko w przypadku widoków oznaczonych jako „dirty” przez wywołanie invalidate. Widoki, które nie zostały unieważnione, można ponownie narysować, ponownie wydając wcześniej zarejestrowaną listę wyświetlania. Nowy model rysowania składa się z 3 etapów:
Unieważnianie hierarchii
Rejestrowanie i aktualizowanie list wyświetlania
Rysowanie list wyświetlania
W tym modelu nie możesz polegać na tym, że widok przecinający obszar wymagający odświeżenia będzie miał wykonaną metodę draw. Aby system Android zarejestrował listę wyświetlania widoku, musisz wywołać invalidate. Jeśli o tym zapomnisz, widok będzie wyglądać tak samo, nawet po wprowadzeniu zmian.
Korzystanie z list wyświetlania poprawia też wydajność animacji, ponieważ ustawianie określonych właściwości, takich jak przezroczystość czy rotacja, nie wymaga unieważniania widoku docelowego (jest to robione automatycznie). Ta optymalizacja dotyczy też widoków z listami wyświetlania (każdego widoku, gdy aplikacja jest akcelerowana sprzętowo). Załóżmy na przykład, że istnieje LinearLayout, który zawiera ListView nad Button. Lista wyświetlania dla elementu LinearLayout wygląda tak:
DrawDisplayList(ListView)DrawDisplayList(Button)
Załóżmy, że chcesz zmienić przezroczystość ListView. Po wywołaniu setAlpha(0.5f) na ListView lista wyświetleń zawiera teraz:
SaveLayerAlpha(0.5)DrawDisplayList(ListView)RestoreDrawDisplayList(Button)
Nie wykonano złożonego kodu rysowania ListView. Zamiast tego system zaktualizował tylko listę wyświetlania znacznie prostszego elementu LinearLayout.
W aplikacji bez włączonego przyspieszenia sprzętowego kod rysowania 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 dostarczanych z Androidem, domyślnych widżetów i układów oraz 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. Te operacje 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 w Twojej aplikacji brakuje którejś z tych funkcji lub występują w niej te ograniczenia, możesz wyłączyć akcelerację sprzętową tylko w przypadku odpowiedniej części aplikacji, wywołując funkcję setLayerType(View.LAYER_TYPE_SOFTWARE, null).
Dzięki temu możesz nadal korzystać z akceleracji sprzętowej w innych miejscach.
Więcej informacji o tym, jak włączać i wyłączać akcelerację sprzętową na różnych poziomach w aplikacji, znajdziesz w artykule Sterowanie akceleracją sprzętową.
Wyświetlanie warstw
We wszystkich wersjach Androida widoki mogą renderować się w buforach poza ekranem, korzystając z pamięci podręcznej rysowania widoku lub z funkcji Canvas.saveLayer. Bufory poza ekranem, czyli warstwy, mają kilka zastosowań. Możesz ich używać, aby zwiększyć wydajność podczas animowania złożonych widoków lub stosowania efektów kompozycji. Możesz na przykład wdrożyć efekty zanikania za pomocą polecenia Canvas.saveLayer, aby tymczasowo renderować widok w warstwie, a następnie ponownie umieścić go na ekranie z czynnikiem krycia.
Od Androida 3.0 (poziom API 11) masz większą kontrolę nad tym, jak i kiedy używać warstw, dzięki metodzie View.setLayerType. Ten interfejs API przyjmuje 2 parametry: typ warstwy, której chcesz użyć, oraz opcjonalny obiekt Paint, który opisuje sposób łączenia warstwy. Za pomocą parametru Paint możesz zastosować do warstwy filtry kolorów, specjalne tryby mieszania lub krycie. Widok może korzystać z jednego z 3 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 do tekstury sprzętowej, jeśli aplikacja jest akcelerowana sprzętowo. Jeśli aplikacja nie korzysta z akceleracji sprzętowej, ten typ warstwy działa tak samo jakLAYER_TYPE_SOFTWARE.LAYER_TYPE_SOFTWARE: widok jest renderowany w oprogramowaniu do postaci mapy bitowej.
Rodzaj używanej warstwy zależy od Twojego celu:
Wydajność: użyj typu warstwy sprzętowej, aby renderować widok w teksturze sprzętowej. Gdy widok zostanie wyrenderowany w warstwie, jego kod rysowania nie musi być wykonywany, dopóki nie wywoła metody
invalidate. Niektóre animacje, takie jak animacje alfa, można następnie zastosować bezpośrednio do warstwy, co jest bardzo wydajne dla procesora graficznego.Efekty wizualne: użyj typu warstwy sprzętowej lub programowej i
Paint, aby zastosować specjalne efekty wizualne do widoku. Możesz na przykład narysować widok w czerni i bieli za pomocąColorMatrixColorFilter.Zgodność: użyj typu warstwy oprogramowania, aby wymusić renderowanie widoku w oprogramowaniu. Jeśli widok, który jest akcelerowany sprzętowo (np. cała aplikacja), ma problemy z renderowaniem, jest to łatwy sposób na obejście ograniczeń potoku renderowania sprzętowego.
Wyświetlanie warstw i animacji
Warstwy sprzętowe mogą zapewniać szybsze i płynniejsze animacje, gdy aplikacja korzysta z akceleracji sprzętowej. Animowanie złożonych widoków, które wykonują wiele operacji rysowania, z częstotliwością 60 klatek na sekundę nie zawsze jest możliwe. Można temu zapobiec, używając warstw sprzętowych do renderowania widoku do tekstury sprzętowej. Tekstura sprzętowa może być następnie używana do animowania widoku, co eliminuje konieczność ciągłego ponownego rysowania widoku podczas animacji. Widok nie jest ponownie rysowany, chyba że zmienisz jego właściwości, co spowoduje wywołanie funkcji invalidate, lub ręcznie wywołasz funkcję invalidate. Jeśli w aplikacji jest uruchomiona animacja, a nie uzyskujesz oczekiwanych płynnych wyników, rozważ włączenie warstw sprzętowych w animowanych widokach.
Gdy widok jest obsługiwany przez warstwę sprzętową, niektóre jego właściwości są określane przez sposób, w jaki warstwa jest komponowana na ekranie. Ustawienie tych właściwości będzie wydajne, ponieważ nie wymaga unieważnienia i ponownego narysowania widoku. Poniżej znajdziesz listę właściwości, które wpływają na sposób łączenia warstwy. Wywołanie funkcji ustawiającej dla dowolnej z tych właściwości powoduje optymalne unieważnienie i brak ponownego rysowania widoku docelowego:
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 punkt początkowy przekształceń warstwy.
Te właściwości to nazwy używane podczas animowania widoku za pomocą ObjectAnimator. Jeśli chcesz uzyskać dostęp do tych właściwości, wywołaj odpowiedni setter lub getter. Aby na przykład zmodyfikować właściwość alfa, wywołaj funkcję
setAlpha. Poniższy fragment kodu pokazuje najbardziej efektywny sposób obracania widoku 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 zużywają pamięć wideo, dlatego zdecydowanie zalecamy włączanie ich tylko na czas trwania animacji, a następnie wyłączanie po jej zakończeniu. Możesz to zrobić za pomocą 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
Przełączenie się na akcelerowaną sprzętowo grafikę 2D może natychmiast zwiększyć wydajność, ale nadal warto projektować aplikację tak, aby efektywnie korzystała z procesora graficznego. W tym celu postępuj zgodnie z tymi rekomendacjami:
- Zmniejsz liczbę widoków w aplikacji
- Im więcej widoków musi wygenerować system, tym wolniej będzie działać. Dotyczy to również potoku renderowania oprogramowania. Zmniejszenie liczby widoków to jeden z najprostszych sposobów optymalizacji interfejsu.
- Unikaj przekroczenia limitu
- Nie rysuj zbyt wielu warstw na sobie. Usuń wszystkie widoki, które są całkowicie zasłonięte przez inne nieprzezroczyste widoki 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. - 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 ustawisz przezroczystość widoku za pomocą
setAlpha,AlphaAnimationlubObjectAnimator, jest on renderowany w buforze poza ekranem, co podwaja wymagane wypełnienie. Jeśli stosujesz wartość alfa w przypadku bardzo dużych widoków, rozważ ustawienie typu warstwy widoku naLAYER_TYPE_HARDWARE.