créditos: https://unsplash.com/photos/wYVfGtbhNSE

Android Performance Series: Entendendo processo de renderização

Paula Rosa
Android Dev BR
Published in
7 min readMar 15, 2021

--

Olá pessoal, este é o início de uma série de posts sobre pequenas otimizações de performance. Neste post você irá entender como o processo de renderização ocorre e quais fatores são relevantes para a performance do app no que se refere à renderização.

Como o android desenha os componentes na tela

Para um objeto ser desenhado na tela ele primeiramente é carregado na CPU (juntamente com suas texturas, resources, etc) e então, após passar por alguns processos na CPU, é então carregado na memória e enviado a GPU, que irá transformar estas texturas em pixels e objetos visualizados na tela. Devido a este processo ser um tanto complexo ele acaba sendo bem custoso para o sistema.

CPU — Central Processing Unit

A CPU atua no processo de renderização através dos layouts. Toda vez que um objeto é desenhado na tela ele passa pelo seguinte processo:

Etapas de desenho de uma view/viewgroup

Measure

Nesta etapa ocorre uma varredura transversal de cima para baixo no layout. Nesta etapa o importante é obter a dimensão (largura e altura) de cada view e ViewGroups do layout. Dependendo do tipo de layout esta medida é feita mais de uma vez.

Layout

Outra varredura transversal ocorre também no sentido de cima para baixo no layout para determinar o posicionamento de cada um dos filhos usando a medida obtida na etapa measure.

Draw

Nesta última etapa ocorre mais uma varredura transversal. Um objeto canvas é criado para cada objeto da árvore do layout e uma lista de comandos conhecida como display list, que contém comandos de desenho além também de possuir valores das medidas e posicionamentos das views e viewgroups que compõem o layout. Depois disto a display list é enviada a GPU.

Caso você se interesse em aprofundar mais neste assunto aconselho assistir este vídeo da Huyen Tue Dao que explica muito bem as custom views e aborda cada etapa detalhadamente.

Cada etapa destas etapas serão abordadas nos próximos artigos. O importante aqui é ter uma noção de como todo este processo ocorre e saber que o layout pode influenciar na performance da nossa aplicação.

Graphic Processing Unit (GPU)

A GPU é a parte do hardware responsável por renderizar os objetos, texturas e cores na tela do smartphone. Ela transforma impulsos elétricos e comandos da CPU em imagens e texturas que você visualiza na tela do celular.

DisplayList

Antes de um objeto ser desenhado precisa ser transformado para um formato que seja interpretado pela GPU. Os objetos são transformados em texturas e polígonos, e passados para a GPU, em forma de uma lista de comandos de desenho denominada display list. Esta display list, além de conter toda informação necessária para renderizar uma view, também contém os comandos necessários para OpenGl executar, quando houver a necessidade de renderizar a view.

Etapas de criação e execução de uma display list

Uma display list é criada e executada quando uma view é criada pela primeira vez. Caso esta view seja solicitada no futuro, o Android irá verificar se a display list utilizada para esta view ainda está válida, caso esteja, somente irá executar a display list novamente, poupando o trabalho de recriá-la.

Por exemplo, resources que são provenientes de um tema, como os drawables por exemplo, serão agrupados como uma textura única e carregados na GPU.

Elementos gráficos obtidos através de resources (drawables, styles)

Esses resources ao serem solicitados novamente possuem uma display list válida, portanto o Android não recriará a display list, e irá executá-la diretamente, fazendo com que seja extremamente rápido carregá-los.

Etapas de execução de uma display lsit quando uma propriedade da view é alterada

Porém, ao alterarmos uma parte visual da view, bem como seu tamanho ou outras propriedades, a display list anterior pode não estar mais válida e o método invalidate() é chamado.

Etapas de criação e execução da display list quando a view o invalidate é chamado

Importante: Ao chamar o método invalidate() você está dizendo ao android que cancele a view existente e faça todo o processo de criação da display list novamente. Com isto, você pode acabar onerando performance, por isso, é importante chamar este método quando realmente fizer sentido.

Esta display list é então enviada para GPU e ocorre um processo chamado de rasterization.

Rasterization

É basicamente a transformação de polígonos proveniente dos componentes visuais e shapes, (adicionados no XML) que são transformados em pixels e mostrados na tela para o usuário. Veja a imagem abaixo para entender melhor.

Processo de rasterização

Se você quiser entender um pouco mais deste processo, indico assistir este vídeo que fala bastante sobre este assunto, da série Android Performance Patterns.

Um pouco de teoria

Para mostrar/movimentar um objeto na tela o Android utiliza o conceito de frames, ou quadros, que são mostrados em sequência nos fornecendo uma sensação de movimento. Você já deve ter ouvido alguma frase “O jogo tal roda a x frames / segundo”. Veja a imagem abaixo:

O olho humano detecta movimentos a partir de 10 a 12 frames/segundo. No entanto, para o olho humano perceber uma transição de forma suave (smoth) o desenho de um objeto na tela precisa estar dentro de 60 frames/segundo.

A quantidade de frames disponível por segundo é uma informação proveniente do hardware, a maioria dos dispositivos hoje fornecem uma atualização de tela de 60 frames/segundo, portanto:

Cálculo demonstrando quantos milissegundos deve ter a execução de cada frame

Ou seja, em 16.666 milissegundos você deve fazer tudo que precisa. Isso inclui:

  • Computação e cálculos
  • Desenho das views na tela
  • Atualização de views na tela
  • Animações e transições
  • Tempo para o garbage collector recolher objetos

Linha dos 16ms e o "Dropped frame"

Imagine os frames como linhas de tempo, onde cada frame tem seus 16ms máximo para executar tudo que lhe é designado e no final deste período o Android irá desenhar o que foi requisitado na tela. Veja a figura abaixo.

Linha do tempo mostrando como ocorre o desenho das views

Agora imagine que existam 5 tarefas para serem executadas no primeiro frame, porém elas demorariam mais que os 16ms. O que acontece é que o frame será removido (dropped) e as tarefas que sobraram serão executadas no próximo frame.

O problema é que a etapa de desenho só acontece no final do frame, se houverem tarefas para serem executadas pode não dar tempo de o draw ser chamado, fazendo com que o frame seja removido, e somente no frame seguinte será possível desenhar (se houver tempo necessário).

Linha do tempo mostrando como os frames são removidos

O que aconteceu aqui é que as tarefas que eram pra ser executadas no primeiro frame só conseguirão ser executadas no terceiro, onde realmente foi possível ser desenhado na tela, ou seja, de 16ms a execução do desenho passou para 48ms, pois a etapa de desenho só aconteceu no final do terceiro frame.

Portanto, é de extrema importância deixar a execução das tarefas dentro destes 16ms, que irá proporcionar uma boa performance de renderização, evitando que a tela pareça travada ou que transições e animações sofram pausa.

Identificando problemas de performance

Cada etapa, desde o momento que criamos o layout até ele efetivamente ser desenhado e mostrado na tela para o usuário pode apresentar problemas de performance. Nos próximos posts vamos conhecer as etapas e saber como atuar nelas.

Não podemos dizer que é fácil encontrar problemas de performance, mas conhecendo as ferramentas corretas e sabendo os pontos onde agir ficará super fácil. Veja abaixo alguns itens que podem apresentar problemas na performance:

Correção de problemas conforme etapa de renderização da view ou layouts

E agora?

Bom, agora que você sabe as causas de problemas relacionados a renderização do app precisamos saber como atuar. A primeira coisa que você tem que ter em mente é que cada caso é um caso, alguns problemas são relacionados à hierarquia de layouts, overdraw, muito processamento na GPU ou CPU ou até mesmo, uma somatória destes fatores.

Encontrar problemas de performance em um app é como diagnosticar um problema de saúde: você só conhece a consequência, como o app estar lento, usuários reclamando, animações travadas etc. Para detectar onde está a raiz do problema é preciso investigar utilizando ferramentas de análise de performance, rodar o app em versões mais antigas (similares aos que seu usuário utiliza) e tentar reduzir ao máximo os pontos críticos da aplicação que tem relação à performance, a fim de melhorar a suavidade das transições, evitar o lagging e travamentos.

Cenas dos próximos capítulos

No próximo post da série veremos as etapas de renderização. Veremos também as ferramentas que possibilitam detectar problemas de performance em cada etapa e é claro, como podemos atuar em cada etapa, a fim de oferecer uma melhor experiência para o usuário.

Deixe aqui um comentário de quais correções de performance vocês gostariam que abordássemos. Abraços ☺

Referências

Documentação oficial

Demais artigos sobre o tema

--

--

Paula Rosa
Android Dev BR

Android developer. Android and accessibility Speaker. Software lover :)