Desempenho e hierarquias da visualização

A maneira como você gerencia a hierarquia dos seus objetos View pode ter um impacto significativo na performance do app. Esta página descreve como avaliar se a hierarquia de visualização está diminuindo a velocidade do app e oferece algumas estratégias para solucionar problemas que possam surgir.

Esta página se concentra na melhoria de layouts baseados em View. Para saber mais sobre como melhorar a performance do Jetpack Compose, consulte Performance do Jetpack Compose.

Performance de layout e avaliação

O pipeline de renderização inclui um estágio de layout e avaliação, em que o sistema posiciona os itens relevantes de maneira adequada na sua hierarquia de visualização. A parte de avaliação desse estágio determina os tamanhos e limites dos objetos View. A parte do layout determina em que área da tela os objetos View serão posicionados.

Os dois estágios do pipeline têm um pequeno custo por visualização ou layout que processam. Na maioria das vezes, o custo é mínimo e não afeta visivelmente a performance. No entanto, ele pode ser maior quando um app adiciona ou remove objetos View, como quando eles são reciclados ou reutilizados por um objeto RecyclerView. O custo também poderá ser maior se um objeto View precisar considerar o redimensionamento para atender às restrições. Por exemplo, se o app chamar SetText() em um objeto View que envolve o texto, talvez o objeto View precise ser redimensionado.

Se casos como esse demorarem muito, eles poderão impedir que um frame seja renderizado dentro dos 16ms permitidos, o que pode causar falhas nos frames e instabilidade na animação.

Como não é possível mover essas operações para uma linha de execução de worker (seu app precisa processá-las na linha de execução principal), é melhor otimizá-las para que levem o mínimo de tempo possível.

Gerenciar layouts complexos

Os Layouts do Android permitem aninhar objetos de interface na hierarquia de visualização. Esse aninhamento também pode impor um custo de layout. Quando o app processa um objeto para layout, ele também executa o mesmo processo em todos os filhos do layout.

Em um layout mais complexo, às vezes um custo surge apenas na primeira vez em que o sistema calcula o layout. Por exemplo, quando seu app recicla um item de lista complexo em um objeto RecyclerView, o sistema precisa expor todos os objetos. Em outro exemplo, mudanças triviais podem se propagar na rede em direção ao pai até atingirem um objeto que não afete o tamanho do pai.

Um motivo comum para o layout demorar muito é quando as hierarquias de objetos View estão aninhadas umas nas outras. Cada objeto de layout aninhado acrescenta custo ao estágio de layout. Quanto mais estável for a hierarquia, menor será o tempo de conclusão do estágio de layout.

Recomendamos o uso do Layout Editor para criar um ConstraintLayout, em vez de RelativeLayout ou LinearLayout, já que geralmente ele é mais eficiente e reduz o aninhamento de layouts. No entanto, para layouts simples que podem ser alcançados usando FrameLayout, recomendamos o uso de FrameLayout.

Se você estiver usando a classe RelativeLayout, talvez seja possível conseguir o mesmo efeito com um custo menor usando visualizações LinearLayout aninhadas e não ponderadas. No entanto, se você estiver usando visualizações LinearLayout aninhadas ponderadas, o custo do layout será muito mais alto, já que requer várias transmissões de layout, conforme explicado na próxima seção.

Também recomendamos usar RecyclerView em vez de ListView, já que ela pode reciclar os layouts de itens individuais da lista, o que é mais eficiente e pode melhorar a performance de rolagem.

Tributação dupla

Normalmente, o framework executa o estágio de layout ou avaliação em uma única transmissão. No entanto, em alguns casos de layouts complicados, o framework pode precisar iterar várias vezes em partes da hierarquia que exigem várias transmissões para serem resolvidas antes de posicionar os elementos. A necessidade de realizar mais de uma iteração de layout e avaliação é chamada de tributação dupla.

Por exemplo, quando você usa o contêiner RelativeLayout, que permite posicionar objetos View em relação às posições de outros objetos View, o framework realiza estas ações:

  1. Executa uma transmissão de layout e avaliação, em que o framework calcula a posição e o tamanho de cada objeto filho com base em cada solicitação filha.
  2. Usa esses dados, considerando também a ponderação dos objetos, para descobrir a posição adequada de visualizações correlacionadas.
  3. Executa uma segunda transmissão de layout para finalizar o posicionamento dos objetos.
  4. Início da próxima etapa do processo de renderização.

Quanto mais níveis sua hierarquia de visualizações tiver, maior será a possível penalidade de desempenho.

Como mencionado, o ConstraintLayout geralmente é mais eficiente que outros layouts, exceto FrameLayout. É menos propenso a várias transmissões de layout e, em muitos casos, elimina a necessidade de aninhá-los.

Contêineres que não são RelativeLayout também podem aumentar a tributação dupla. Por exemplo:

  • Uma visualização LinearLayout poderá resultar em uma transmissão dupla de layout e avaliação se você a deixar na horizontal. Uma transmissão dupla de layout e avaliação também poderá ocorrer na orientação vertical se measureWithLargestChild for adicionado. Nesse caso, o framework talvez precise fazer uma segunda transmissão para resolver os tamanhos adequados dos objetos.
  • O GridLayout também permite o posicionamento relativo, mas normalmente evita a tributação dupla com o pré-processamento de relações de posição entre visualizações filhas. No entanto, se o layout usar ponderações ou preenchimento com a classe Gravity, o benefício do pré-processamento será perdido, e o framework poderá precisar executar várias transmissões se o contêiner for um RelativeLayout.

Várias transmissões de layout e avaliação não causam necessariamente uma sobrecarga para a performance. No entanto, elas poderão se tornar um fardo se estiverem no lugar errado. Tenha cuidado com situações em que uma das condições abaixo pode se aplicar ao contêiner:

  • Ele é um elemento raiz na hierarquia de visualização.
  • Ele tem uma hierarquia de visualização profunda abaixo de si.
  • Há várias instâncias dele preenchendo a tela, de forma semelhante a filhos em um objeto ListView.

Diagnosticar problemas de hierarquia de visualização

A performance do layout é um problema complexo com muitos aspectos. As ferramentas abaixo podem identificar onde os gargalos de performance estão ocorrendo. Algumas fornecem informações menos definitivas, mas podem ter dicas úteis.

Perfetto

O Perfetto é uma ferramenta que apresenta dados sobre a performance. Você pode analisar esses rastros na interface do Perfetto (link em inglês).

Criar perfil de renderização de GPU

A ferramenta de Criação do perfil de renderização de GPU do dispositivo, disponível em dispositivos com Android 6.0 (nível 23 da API) e mais recentes, pode apresentar informações concretas sobre gargalos de performance. Ela permite conferir quanto tempo o estágio de layout e avaliação leva para cada frame de renderização (vídeo em inglês). Esses dados ajudam a diagnosticar problemas de performance de execução e determinar quais problemas de layout e avaliação você precisa solucionar.

Na representação gráfica dos dados capturados, a Criação do perfil de renderização de GPU usa a cor azul para representar o tempo do layout. Para saber mais sobre como usar essa ferramenta, consulte o guia Criação do perfil de velocidade de renderização de GPU.

Lint

A ferramenta Lint do Android Studio pode ajudar a entender as ineficiências da hierarquia de visualização. Para usar essa ferramenta, selecione Analyze > Inspect Code, conforme mostrado na Figura 1.

Figura 1. Selecione Inspect Code no Android Studio.

Confira informações sobre vários itens de layout em Android > Lint > Performance. Para saber mais, clique em cada item para expandi-lo e mostrar mais informações no painel do lado direito da tela. A Figura 2 mostra um exemplo de informação expandida.

Figura 2. Visualização de informações sobre problemas específicos identificados pela ferramenta Lint.

Clicar em um item revela problemas associados a ele no painel à direita.

Para entender mais sobre questões e temas específicos dessa área, consulte a documentação do Lint.

Layout Inspector

A ferramenta Layout Inspector do Android Studio oferece uma representação visual da hierarquia de visualização do app. Essa é uma boa maneira de navegar pela hierarquia dele, tendo uma representação nítida da cadeia mãe de uma exibição específica e inspecionando os layouts criados pelo app.

As visualizações que o Layout Inspector apresenta também podem ajudar a identificar problemas de performance que surgem com a tributação dupla. Ele também pode oferecer uma maneira de identificar cadeias profundas de layouts aninhados ou áreas de layout com uma grande quantidade de filhos aninhados, que podem atrapalhar a performance. Nesses casos, os estágios de layout e avaliação podem ser custosos e resultar em problemas de performance.

Para saber mais, consulte Depurar seu layout com o Layout Inspector e a Validação de layout.

Resolver problemas de hierarquia de visualização

O conceito fundamental por trás da solução de problemas de performance que surgem nas hierarquias de visualização pode ser difícil na prática. Impedir que hierarquias de visualização imponham penalidades de performance consiste em estabilizar sua hierarquia e reduzir a tributação dupla. Esta seção discute algumas estratégias para alcançar esses objetivos.

Remover layouts aninhados redundantes

ConstraintLayout é uma biblioteca do Jetpack com um grande número de mecanismos diferentes para posicionar visualizações no layout. Isso reduz a necessidade de aninhar um ConstaintLayout e pode ajudar a nivelar a hierarquia de visualização. Geralmente, é mais simples nivelar hierarquias usando ConstraintLayout em comparação com outros tipos de layout.

Os desenvolvedores geralmente usam mais layouts aninhados do que o necessário. Por exemplo, um contêiner RelativeLayout pode incluir um único filho que também é um contêiner RelativeLayout. Esse aninhamento é redundante e acrescenta custos desnecessários à hierarquia de visualização. O Lint geralmente sinaliza esse problema para você, reduzindo o tempo de depuração.

Adotar merge ou include

Uma causa frequente de redundância no aninhamento de layouts é a tag <include>. Por exemplo, você pode definir um layout reutilizável desta maneira:

<LinearLayout>
    <!-- some stuff here -->
</LinearLayout>

Em seguida, é possível adicionar uma tag <include> para incluir este item no contêiner pai:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/app_bg"
    android:gravity="center_horizontal">

    <include layout="@layout/titlebar"/>

    <TextView android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:text="@string/hello"
              android:padding="10dp" />

    ...

</LinearLayout>

Isso aninha desnecessariamente o primeiro layout no segundo.

A tag <merge> pode ajudar a evitar esse problema. Para saber mais sobre essa tag, consulte Usar a tag <merge>.

Adotar um layout mais barato

Talvez não seja possível ajustar o esquema de layout existente para que ele não contenha layouts redundantes. Em alguns casos, a única solução pode ser estabilizar a hierarquia alternando para um tipo de layout totalmente diferente.

Por exemplo, TableLayout oferece a mesma funcionalidade que um layout mais complexo com várias dependências de posição. A biblioteca do Jetpack ConstraintLayout oferece uma funcionalidade semelhante à RelativeLayout, além de outros recursos para ajudar a criar layouts mais planos e eficientes.