Do Android 3.0 (API de nível 11) em diante, o pipeline de renderização 2D do Android tem suporte à aceleração
de hardware. Isso significa que todas as operações de desenho realizadas em uma
tela de View
usam a GPU. Devido à grande quantidade de recursos necessários para
ativar a aceleração de hardware, o app vai consumir mais memória RAM.
Por padrão, a aceleração de hardware vai ser ativada se o nível desejado da API for maior que ou igual a 14, mas ela também
pode ser ativada de maneira explícita. Se o aplicativo usar apenas visualizações e
Drawable
s padrão, a ativação global não vai causar efeitos adversos para os
desenhos. No entanto, como a aceleração de hardware não tem suporte para todas as operações de desenho 2D,
a ativação pode afetar algumas das suas chamadas de desenho ou visualizações personalizadas. Em geral,
os problemas se manifestam como elementos invisíveis, exceções ou pixels renderizados de maneira incorreta. Para
resolver isso, o Android oferece a opção de ativar ou desativar a aceleração de hardware em vários
níveis. Consulte Controlar a aceleração de hardware.
Se o aplicativo faz desenhos personalizados, ele precisa ser testado em dispositivos de hardware reais com a aceleração de hardware ativada para que você possa encontrar possíveis problemas. A seção Suporte para operações de desenho descreve problemas conhecidos da aceleração de hardware e como solucionar todos eles.
Consulte também OpenGL com as APIs Framework e Renderscript.
Controlar a aceleração de hardware
Você pode controlar a aceleração de hardware nos seguintes níveis:
- Aplicativo
- Atividade
- Janela
- Visualização
Nível do aplicativo
No arquivo de manifesto do Android, adicione o seguinte atributo à tag
<application>
para ativar a aceleração de hardware para todo o
aplicativo:
<application android:hardwareAccelerated="true" ...>
Nível da atividade
Se o aplicativo não se comporta da forma correta com a aceleração de hardware globalmente ativada, ela também
pode ser controlada para atividades específicas. Se quiser ativar ou desativar a aceleração de hardware no
nível de atividade, use o atributo android:hardwareAccelerated
para
o elemento
<activity>
. O exemplo a seguir ativa a aceleração de hardware para
todo o aplicativo, mas a desativa para uma atividade:
<application android:hardwareAccelerated="true"> <activity ... /> <activity android:hardwareAccelerated="false" /> </application>
Nível da janela
Se você precisa de um controle ainda mais refinado, pode ativar a aceleração de hardware para uma janela específica com o seguinte código:
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);
Observação: no momento, não é possível desativar a aceleração de hardware no nível da janela.
Nível da visualização
É possível desativar a aceleração de hardware para uma visualização específica no momento da execução com o seguinte código:
Kotlin
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
Java
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
Observação: no momento, não é possível ativar a aceleração de hardware no nível da visualização. As camadas de visualização têm outras funções além de desativar a aceleração de hardware. Consulte Camadas de visualização para ver mais informações sobre os usos relacionados.
Determinar se uma visualização tem aceleração de hardware
Em alguns casos é importante que o aplicativo saiba se está com aceleração de hardware, especialmente para itens como visualizações personalizadas. Isso é particularmente útil se o app faz muitos desenhos personalizados e nem todas as operações têm suporte ao novo pipeline de renderização.
Há duas maneiras de verificar se o aplicativo tem aceleração de hardware:
View.isHardwareAccelerated()
vai retornar o valortrue
se aView
estiver anexada a uma janela com aceleração de hardware.Canvas.isHardwareAccelerated()
vai retornar o valortrue
se aCanvas
estiver com aceleração de hardware.
Se você precisa fazer essa verificação no código de desenho, use
Canvas.isHardwareAccelerated()
em vez de
View.isHardwareAccelerated()
sempre que possível. Quando uma visualização
é anexada a uma janela com aceleração de hardware, ela ainda pode ser desenhada usando uma tela sem a
aceleração. Isso acontece, por exemplo, ao desenhar uma visualização em um bitmap para fins de
armazenamento em cache.
Modelos de desenho do Android
Quando a aceleração de hardware está ativada, o framework do Android usa um novo modelo de desenho que utiliza listas de exibição para renderizar o aplicativo na tela. Para entender melhor as listas de exibição e como elas podem afetar seu aplicativo, é preciso compreender como o Android também desenha visualizações sem a aceleração de hardware. As seções a seguir descrevem os modelos de desenho baseados em software e com aceleração de hardware.
Modelo de desenho baseado em software
No modelo de desenho do software, estas são as etapas seguidas para visualizações:
- Invalidar a hierarquia
- Desenhar a hierarquia
Sempre que um aplicativo precisa atualizar uma parte da IU, ele invoca invalidate()
(ou uma das variantes) em qualquer visualização em que um conteúdo tenha
mudado. As mensagens de invalidação são propagadas até a hierarquia de visualizações para computar
as regiões da tela que precisam ser redesenhadas (a região suja). Em seguida, o sistema Android
desenha todas as visualizações na hierarquia que se cruzam com a região suja. Infelizmente, há
duas desvantagens nesse modelo de desenho:
- Primeiramente, ele requer a execução de uma grande quantidade de código em cada transmissão de desenho. Por exemplo, se
o aplicativo chamar
invalidate()
em um botão que estiver sobre outra visualização, o sistema Android vai desenhar a visualização novamente mesmo que ela não tenha mudado. - O segundo problema é que o modelo de desenho pode ocultar bugs no seu aplicativo. Como o
sistema do Android redesenha as visualizações quando elas se cruzam com a região suja, uma visualização com conteúdo
mudado pode ser desenhada novamente mesmo que o
invalidate()
não tenha sido chamado. Quando isso acontece, é preciso que outra visualização seja invalidada para que você consiga alcançar o comportamento correto. Esse comportamento pode mudar toda vez que você modifica seu aplicativo. Por isso, chame oinvalidate()
nas suas visualizações personalizadas sempre que modificar dados ou estados que afetam o código de desenho da visualização.
Observação: as visualizações do Android chamam o invalidate()
automaticamente quando as propriedades delas mudam, como a cor
de fundo ou o texto de uma TextView
.
Modelo de desenho com aceleração de hardware
O sistema Android ainda usa invalidate()
e draw()
para solicitar atualizações de tela e renderizar visualizações, mas processa o
desenho real de forma diferente. Em vez de executar os comandos de desenho imediatamente, esse sistema
os registra em listas de exibição, que contêm a saída do código de desenho da
hierarquia da visualização. Outra otimização é que o sistema Android só precisa registrar e atualizar
listas de exibição para as visualizações marcadas como sujas por uma chamada
invalidate()
. As visualizações que não foram invalidadas podem ser redesenhadas com uma nova emissão da lista
registrada anteriormente. O novo modelo de desenho tem três etapas:
- Invalidar a hierarquia
- Registrar e atualizar as listas de exibição.
- Desenhar as listas de exibição
Com esse modelo, não é possível contar com uma visualização que cruza a região suja para que o método draw()
correspondente seja executado. Para garantir que o sistema Android registre a
lista de exibição de uma visualização, é preciso chamar invalidate()
. Se isso
não for feito, a visualização vai ficar igual mesmo depois de ter sido mudada.
O uso de listas de exibição também beneficia a performance da animação, já que a configuração de propriedades específicas,
como Alfa ou rotação, não exige a invalidação da visualização de destino. Isso é feito
de forma automática. Essa otimização também é válida para visualizações com listas de exibição, ou seja, qualquer visualização caso seu
aplicativo tenha aceleração de hardware. Por exemplo, suponha que há um LinearLayout
que contém uma ListView
acima de um Button
. A lista de exibição do LinearLayout
tem esta
aparência:
- DrawDisplayList(ListView)
- DrawDisplayList(Button)
Agora imagine que você queira mudar a opacidade da ListView
. Depois de
invocar setAlpha(0.5f)
na ListView
, a lista de exibição vai conter
o seguinte:
- SaveLayerAlpha(0.5)
- DrawDisplayList(ListView)
- Restore
- DrawDisplayList(Button)
O código de desenho complexo da ListView
não foi executado. Em vez disso, o
sistema só atualizou a lista de exibição do LinearLayout
, que é muito mais simples. Em
um aplicativo sem a aceleração de hardware ativada, o código de desenho da lista e da mãe
dela é executado novamente.
Suporte a operações de desenho
Quando acelerado por hardware, o pipeline de renderização 2D tem suporte às operações de desenho Canvas
mais usadas, assim como a várias operações menos comuns. Há suporte
para todas as operações de desenho usadas para renderizar aplicativos que vêm com o Android, widgets
e layouts padrão e efeitos visuais avançados comuns, como reflexos e texturas
ladrilhadas.
A tabela a seguir descreve o suporte de várias operações em diversos níveis de API:
Primeiro nível da API com suporte | ||||
Canvas | ||||
drawBitmapMesh() (matriz de cores) | 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() com rotação/perspectiva | 18 | |||
Paint | ||||
setAntiAlias() (para texto) | 18 | |||
setAntiAlias() (para linhas) | 16 | |||
setFilterBitmap() | 17 | |||
setLinearText() | ✗ | |||
setMaskFilter() | ✗ | |||
setPathEffect() (para linhas) | 28 | |||
setShadowLayer() (que não seja texto) | 28 | |||
setStrokeCap() (para linhas) | 18 | |||
setStrokeCap() (para pontos) | 19 | |||
setSubpixelText() | 28 | |||
Xfermode | ||||
PorterDuff.Mode.DARKEN (framebuffer) | 28 | |||
PorterDuff.Mode.LIGHTEN (framebuffer) | 28 | |||
PorterDuff.Mode.OVERLAY (framebuffer) | 28 | |||
Shader | ||||
ComposeShader dentro do ComposeShader | 28 | |||
Mesmo tipo de sombreadores dentro do ComposeShader | 28 | |||
Matriz local no ComposeShader | 18 |
Escalonamento da tela
O pipeline de renderização 2D acelerado por hardware foi criado para oferecer suporte aos desenhos não dimensionados, com algumas operações de desenho degradando significativamente a qualidade em valores de escala maiores. Essas operações são implementadas como texturas desenhadas na escala 1.0, transformadas pela GPU. Do nível 28 da API em diante, todas as operações de desenho podem ser escalonadas sem problemas.
A tabela a seguir mostra quando houve a mudança da implementação para processar grandes escalas corretamente:Operação de desenho a ser escalonada | Primeiro nível da API com suporte |
drawText() | 18 |
drawPosText() | 28 |
drawTextOnPath() | 28 |
Formas simples* | 17 |
Formas complexas* | 28 |
drawPath() | 28 |
Camada de sombra | 28 |
Observação: as formas "simples" são comandos drawRect()
,
drawCircle()
, drawOval()
, drawRoundRect()
e
drawArc()
(com useCenter=false) emitidos com um objeto Paint sem um
PathEffect e não contêm junções não padrão (via setStrokeJoin()
/
setStrokeMiter()
). Outras instâncias desses comandos de desenho se enquadram em "complexas" no
gráfico acima.
Se o aplicativo for afetado por um desses recursos ou limitações ausentes, vai ser possível
desativar a aceleração de hardware apenas para a parte afetada do aplicativo chamando
setLayerType(View.LAYER_TYPE_SOFTWARE, null)
. Dessa forma, você ainda
poderá aproveitar a aceleração de hardware em qualquer outro lugar. Consulte Controlar a aceleração de hardware para ver mais informações sobre como ativar
e desativar esse recurso em diferentes níveis no aplicativo.
Camadas de visualização
Em todas as versões do Android, as visualizações tinham a capacidade de renderização em buffers fora da tela,
seja usando o cache de desenho de uma visualização ou Canvas.saveLayer()
. Os buffers, ou camadas, fora da tela têm vários usos. Eles podem ser usados para
melhorar a performance ao animar visualizações complexas ou usar efeitos de composição. Por exemplo,
você pode implementar efeitos de esmaecimento usando Canvas.saveLayer()
para renderizar de forma temporária uma visualização
em uma camada e depois compor essa visualização novamente na tela com um fator de opacidade.
Do Android 3.0 (API de nível 11) em diante, você tem mais controle sobre como e quando usar camadas
com o método View.setLayerType()
. Essa API usa dois
parâmetros: o tipo de camada que você quer utilizar e um objeto Paint
opcional que descreve como a camada vai ser composta. Use o parâmetro Paint
para aplicar filtros de cor, modos de mesclagem especiais ou opacidade a
uma camada. Uma visualização pode usar um de três tipos de camada:
LAYER_TYPE_NONE
: a visualização é renderizada normalmente e não é protegida por um buffer fora da tela. Esse é o comportamento padrão.LAYER_TYPE_HARDWARE
: a visualização vai ser renderizada no hardware em uma textura se o aplicativo for acelerado por hardware. Se o app não tiver aceleração de hardware, esse tipo de camada vai se comportar da mesma forma queLAYER_TYPE_SOFTWARE
.LAYER_TYPE_SOFTWARE
: a visualização vai ser renderizada em software em um bitmap.
O tipo de camada usado depende do seu objetivo:
- Performance: use um tipo de camada de hardware para renderizar uma visualização em uma textura
de hardware. Quando uma visualização é renderizada em uma camada, o código de desenho não precisa ser executado
até que a visualização chame o
invalidate()
. Algumas animações, como as Alfa, podem ser aplicadas diretamente na camada, o que é muito eficiente para a GPU. - Efeitos visuais: use um tipo de camada de hardware ou software e um
Paint
para aplicar tratamentos visuais especiais a uma visualização. Por exemplo, você pode desenhar uma visualização em preto e branco usando umColorMatrixColorFilter
. - Compatibilidade: use um tipo de camada de software para forçar a renderização de uma visualização no software. Se uma visualização com aceleração de hardware, por exemplo, todo um aplicativo com essa função, está apresentando problemas de renderização, essa é uma maneira fácil de contornar as limitações do pipeline de processamento de hardware.
Animações e camadas de visualização
As camadas de hardware podem proporcionar animações mais rápidas e uniformes quando seu aplicativo
é acelerado por hardware. A execução de uma animação a 60 quadros por segundo nem sempre é possível ao
animar visualizações complexas que geram muitas operações de desenho. Isso pode ser minimizado com
o uso de camadas de hardware que renderizam a visualização para uma textura de hardware. Assim, a textura de hardware pode
ser usada para animar a visualização, eliminando a necessidade de sempre repetir o desenho quando ela
estiver sendo animada. A visualização não é redesenhada, a menos que você altere a
propriedades, que chamará invalidate()
, ou se você chamar invalidate()
manualmente. Se você estiver executando uma animação no
aplicativo e não tiver os resultados uniformes esperados, ative as camadas de hardware nas
visualizações animadas.
Quando uma visualização tem uma camada de hardware, algumas propriedades são processadas pela forma como a camada é composta na tela. A definição dessas propriedades é eficiente, porque elas não exigem que a visualização seja invalidada e redesenhada. A lista de propriedades a seguir afeta o modo como a camada é composta. A chamada do setter de qualquer uma dessas propriedades vai resultar em uma invalidação ideal, ou seja, a visualização de destino não será redesenhada:
alpha
: muda a opacidade da camada.x
,y
,translationX
,translationY
: muda a posição da camada.scaleX
,scaleY
: muda o tamanho da camada.rotation
,rotationX
,rotationY
: muda a orientação da camada no espaço 3D.pivotX
,pivotY
: muda a origem das transformações da camada.
Essas propriedades são os nomes usados ao animar uma visualização com um ObjectAnimator
. Se você quiser acessar essas propriedades, chame o setter ou
o getter adequado. Por exemplo, para modificar a propriedade Alfa, chame setAlpha()
. O snippet de código a seguir mostra a melhor maneira
de rotacionar uma visualização em 3D em torno do eixo 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();
Como as camadas de hardware consomem memória de vídeo, é altamente recomendável que você as ative apenas durante a animação e as desative quando ela terminar. Para fazer isso, use listeners de animação:
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();
Consulte a página Animação de propriedades para mais informações.
Dicas e sugestões
A mudança para gráficos 2D acelerados por hardware pode aumentar a performance de forma instantânea. Mesmo assim, siga estas recomendações e projete seu aplicativo para que ele use a GPU de modo eficaz:
- Reduza o número de visualizações no aplicativo
- Quanto mais visualizações o sistema tiver que desenhar, mais lento ele vai ficar. Isso também é válido para o pipeline de renderização de software. Reduzir as visualizações é uma das maneiras mais fáceis de otimizar sua IU.
- Evite o overdraw
- Não desenhe muitas camadas colocando umas em cima das outras. Remova todas as visualizações completamente encobertas por outras visualizações opacas. Se você precisar desenhar várias camadas combinadas umas sobre as outras, mescle todas elas em uma única camada. Uma regra de ouro com o hardware atual é não desenhar mais que 2,5 vezes o número de pixels na tela por frame (pixels transparentes em uma contagem de bitmap).
- Não crie objetos de renderização em métodos de desenho
- Um erro comum é criar um novo objeto
Paint
ou um novoPath
sempre que um método de renderização é invocado. Isso força o coletor de lixo a ser executado com mais frequência, além de ignorar os caches e as otimizações no pipeline de hardware. - Não modifique formas com muita frequência
- Formas, caminhos e círculos complexos, por exemplo, são renderizados usando máscaras de textura. Toda vez que você cria ou modifica um caminho, o pipeline de hardware cria uma nova máscara, o que pode ser custoso.
- Não modifique bitmaps com muita frequência
- Sempre que você mudar o conteúdo de um bitmap, ele vai ser enviado novamente como uma textura de GPU na próxima vez que for desenhado.
- Use a Alfa com cuidado
- Quando você deixa uma visualização translúcida usando
setAlpha()
,AlphaAnimation
ouObjectAnimator
, ela é renderizada em um buffer fora da tela que dobra a taxa de preenchimento necessária. Ao usar a Alfa em visualizações muito grandes, defina o tipo de camada da visualização comoLAYER_TYPE_HARDWARE
.