Aceleração de hardware

A partir do Android 3.0 (API nível 11), o pipeline de renderização 2D do Android é compatível com a aceleração de hardware. Isso significa que todas as operações de desenho realizadas em uma tela de View usam a GPU. Devido ao aumento dos recursos necessários para ativar a aceleração de hardware, seu app consumirá mais memória RAM.

Por padrão, a aceleração de hardware será ativada se o nível da API de destino for >=14, mas ela também poderá ser ativada explicitamente. Se seu aplicativo usar apenas visualizações e Drawables padrão, a ativação global não causará efeitos adversos para os desenhos. No entanto, como a aceleração de hardware não é compatível com todas as operações de desenho 2D, a ativação pode afetar algumas das suas visualizações personalizadas ou chamadas de desenho. Em geral, os problemas se manifestam como elementos invisíveis, exceções ou pixels renderizados incorretamente. 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 seu aplicativo realizar desenhos personalizados, teste-o em dispositivos de hardware reais com a aceleração de hardware ativada para procurar possíveis problemas. A seção Operações de desenho não compatíveis descreve problemas conhecidos de aceleração de hardware e como contorná-los.

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 seu aplicativo não se comporta corretamente com a aceleração de hardware globalmente ativada, também é possível controlá-la para atividades individuais. Para 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ê precisar de um controle ainda mais refinado, poderá 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 única visualização 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á acelerado por hardware, especialmente para itens como visualizações personalizadas. Isso é particularmente útil se seu aplicativo faz muitos desenhos personalizados e nem todas as operações são compatíveis com o novo pipeline de renderização.

Há duas maneiras de verificar se o aplicativo tem aceleração de hardware:

Se você precisar 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 acelerada por hardware, ela ainda pode ser desenhada por meio de uma tela não acelerada por hardware. 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 acelerados por hardware.

Modelo de desenho baseado em software

No modelo de desenho do software, as visualizações são desenhadas com as duas etapas a seguir:

  1. Invalidar a hierarquia.
  2. Desenhar a hierarquia.

Sempre que um aplicativo precisa atualizar uma parte da IU, ele invoca invalidate() (ou uma das variantes) em qualquer visualização que tenha conteúdo alterado. 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 cruzam com a região suja. Infelizmente, há duas desvantagens nesse modelo de desenho:

  • Primeiramente, esse modelo requer a execução de uma grande quantidade de código em cada passagem de desenho. Por exemplo, se o aplicativo chamar invalidate() em um botão que estiver sobre outra visualização, o sistema Android 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 cruzam com a região suja, uma visualização cujo conteúdo foi alterado pode ser redesenhada, mesmo que invalidate() não tenha sido chamado. Quando isso acontece, é preciso que outra visualização seja invalidada para que você consiga o comportamento correto. Esse comportamento poderá mudar sempre que você modificar seu aplicativo. Por isso, sempre chame invalidate() nas suas visualizações personalizadas quando modificar dados ou estados que afetem o código de desenho da visualização.

Observação: as visualizações do Android chamam automaticamente invalidate() quando as propriedades delas mudam, como a cor do plano de fundo ou o texto de uma TextView.

Modelo de desenho acelerado por 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, o sistema Android 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 simplesmente com uma nova emissão da lista de exibição registrada anteriormente. O novo modelo de desenho contém três etapas:

  1. Invalidar a hierarquia.
  2. Registrar e atualizar as listas de exibição.
  3. 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 terá a mesma aparência mesmo depois de ter sido alterada.

O uso de listas de exibição também beneficia o desempenho da animação, já que a configuração de propriedades específicas, como Alfa ou rotação, não requer a invalidação da visualização de destino (isso é feito automaticamente). Essa otimização também é válida para visualizações com listas de exibição (qualquer visualização quando seu aplicativo tem 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 é semelhante a:

  • 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 conterá o seguinte:

  • SaveLayerAlpha(0.5)
  • DrawDisplayList(ListView)
  • Restore
  • DrawDisplayList(Button)

O código de desenho complexo de ListView não foi executado. Em vez disso, o sistema atualizou apenas 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.

Operações de desenho não compatíveis

Quando é acelerado por hardware, o pipeline de renderização 2D é compatível com as operações de desenho Canvas mais usadas, e também com muitas operações menos comuns. 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 reflexões e texturas ladrilhadas, são compatíveis.

A tabela a seguir descreve a compatibilidade de várias operações em diversos níveis de API:

Primeiro nível da API compatível
Tela
drawBitmapMesh() (matriz de cores) 18
drawPicture() 23
drawPosText() 16
drawTextOnPath() 16
drawVertices()
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
Pintura
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
Sombreador
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 primeiramente para oferecer compatibilidade com desenhos não dimensionados, com algumas operações de desenho degradando significativamente a qualidade em valores de escala mais altos. Essas operações são implementadas como texturas desenhadas na escala 1.0, transformadas pela GPU. A partir do nível 28 da API, todas as operações de desenho podem ser escalonadas sem problemas.

A tabela a seguir mostra quando a implementação foi alterada para processar grandes escalas corretamente:
Operação de desenho a ser escalonada Primeiro nível da API compatível
drawText() 18
drawPosText() 28
drawTextOnPath() 28
Formas simples* 17
Formas complexas* 28
drawPath() 28
Camada de sombra 28

Observação:: formas "simples" são comandos drawRect(), drawCircle(), drawOval(), drawRoundRect() e drawArc() (com useCenter=false) emitidos com uma pintura sem 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 seu aplicativo for afetado por um desses recursos ou limitações ausentes, 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 podiam renderizar em buffers fora da tela, seja usando o cache de desenho de uma visualização ou Canvas.saveLayer(). Buffers, ou camadas, fora da tela têm vários usos. Você pode usá-los para melhorar o desempenho ao animar visualizações complexas ou usar efeitos de composição. Por exemplo, você pode implementar efeitos de esmaecimento usando Canvas.saveLayer() para renderizar temporariamente uma visualização em uma camada e, em seguida, compô-la novamente na tela com um fator de opacidade.

A partir do Android 3.0 (API nível 11), 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 usar e um objeto Paint opcional que descreve como a camada deve 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 será renderizada no hardware em uma textura se o aplicativo for acelerado por hardware. Se o aplicativo não tiver aceleração de hardware, esse tipo de camada se comportará da mesma forma que LAYER_TYPE_SOFTWARE.
  • LAYER_TYPE_SOFTWARE: a visualização é renderizada em software em um bitmap.

O tipo de camada usado depende do seu objetivo:

  • Desempenho: 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 invalidate(). Algumas animações, como animações 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 uma Paint para aplicar tratamentos visuais especiais a uma visualização. Por exemplo, você pode desenhar uma visualização em preto e branco usando um ColorMatrixColorFilter.
  • 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, se todo o aplicativo é acelerado por hardware) está apresentando problemas de renderização, essa é uma maneira fácil de contornar as limitações do pipeline de processamento de hardware.

Camadas e animações de visualização

As camadas de hardware podem oferecer 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 para renderizar 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 repetir o desenho constantemente quando ela estiver sendo animada. A visualização não será redesenhada, a menos que você mude as propriedades da visualização, o que chamará invalidate(), ou chame 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 suas 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 será 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 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 somente durante a animação e as desative quando a animação 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();
    

Para mais informações sobre a animação de propriedades, consulte Animação de propriedades.

Dicas e sugestões

A mudança para gráficos 2D acelerados por hardware pode aumentar o desempenho instantaneamente. Mesmo assim, projete seu aplicativo para que ele use a GPU de forma eficaz. Siga estas recomendações:

Reduza o número de visualizações no aplicativo
Quanto mais visualizações o sistema tiver que desenhar, mais lento ele será. Isso também é válido para o pipeline de renderização de software. Reduzir visualizações é uma das maneiras mais fáceis de otimizar sua IU.
Evite o overdraw
Não desenhe muitas camadas em cima umas das outras. Remova todas as visualizações completamente encobertas por outras visualizações opacas. Se você precisar desenhar várias camadas mescladas umas sobre as outras, mescle-as 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 uma nova Paint ou um novo Path 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 por meio de 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 caro.
Não modifique bitmaps com muita frequência
Sempre que você mudar o conteúdo de um bitmap, ele será enviado novamente como uma textura de GPU na próxima vez que for desenhado.
Use a Alfa com cuidado
Quando você torna uma visualização translúcida usando setAlpha(), AlphaAnimation ou ObjectAnimator, 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 como LAYER_TYPE_HARDWARE.