The Android Developer Challenge is back! Submit your idea before December 2.

Hierarquias de desempenho e visualização

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

Desempenho 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 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 de layout determina em que área da tela posicionar os objetos View.

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 é não afeta visivelmente o desempenho. Entretanto, 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 mais alto se um objeto View precisar considerar o redimensionamento devido a restrições: por exemplo, se seu app chamar SetText() em um objeto View que ajusta texto, o View pode precisar ser redimensionado.

Se casos como esse demorarem muito, eles podem impedir que um frame seja renderizado dentro dos 16 ms permitidos para que os frames sejam descartados e a animação se torne instável.

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

Gerenciar complexidade: os layouts são importantes

Os Layouts do Android permitem aninhar objetos de IU na hierarquia de visualização. Esse aninhamento também pode impor um custo de layout. Quando o app processa um objeto para o layout, ele realiza o mesmo processo em todos os filhos do layout. Para 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 dispor todos os objetos. Em outro exemplo, alterações triviais podem se propagar na rede até o pai até atingirem um objeto que não afeta o tamanho do pai.

O caso mais comum em que o layout demora muito tempo é quando as hierarquias de objetos View sã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.

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. Além disso, se seu app for destinado ao Android 7.0 (API de nível 24), você provavelmente poderá usar um editor especial de layouts para criar um objeto ConstraintLayout em vez de RelativeLayout. Fazer isso permitirá que você evite muitos dos problemas descritos nesta seção. A classe ConstraintLayout oferece um controle de layouts parecido, mas com um desempenho muito melhor. Essa classe usa o próprio sistema de resolução de restrições para resolver relacionamentos entre visualizações de uma maneira muito diferente de layouts padrão.

Tributação dupla

Geralmente, o framework executa o estágio de avaliação ou layout rapidamente e em uma única passagem. No entanto, em alguns casos de layout mais complicados, o framework pode precisar iterar várias vezes em partes da hierarquia que precisam de várias passagens para resolução antes de posicionar os elementos. Ter que realizar mais de uma iteração de layout e avaliação é chamado de tributação dupla.

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

  1. Execução de uma passagem 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. Uso desses dados, considerando também a ponderação dos objetos, para descobrir a posição adequada de visualizações correlacionadas.
  3. Execução de uma segunda passagem 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.

Contêineres que não são RelativeLayout também poderão avançar para a tributação dupla. Por exemplo:

  • Uma visualização LinearLayout poderá resultar em uma passagem dupla de layout e avaliação se você deixá-la na horizontal. Uma passagem dupla de layout e avaliação também pode ocorrer em uma orientação vertical se measureWithLargestChild for adicionado. Nesse caso, o framework pode precisar fazer uma segunda passagem para resolver os tamanhos adequados dos objetos.
  • O GridLayout tem um problema parecido. Embora esse contêiner também permita o posicionamento relativo, ele evita a tributação dupla por meio do pré-processamento de relações de posição entre as visualizações filhas. No entanto, se o layout usar ponderações ou preencher com a classe Gravity, o benefício do pré-processamento se perderá e o framework poderá precisar executar várias passagens se o contêiner for um RelativeLayout.

Várias passagens de layout e avaliação não são, por si só, uma sobrecarga no desempenho. Porém, elas podem se tornar um problema se estiverem no lugar errado. É importante ter cuidado nas situações em que uma das condições a seguir se aplicar ao seu 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

O desempenho do layout é um problema complexo com muitos aspectos. Há algumas ferramentas que podem fornecer boas indicações de onde os gargalos de desempenho estão ocorrendo. Outras ferramentas trazem informações menos definitivas, mas também podem fornecer dicas úteis.

Systrace

Uma ferramenta que fornece ótimos dados sobre o desempenho é o Systrace, que foi criado no SDK do Android. A ferramenta Systrace permite a coleta e inspeção de informações de tempo em um dispositivo Android inteiro. Isso possibilita que você veja quando problemas no desempenho do layout geram problemas no desempenho. Para mais informações sobre o Systrace, consulte a visão geral do rastreamento do sistema.

Criar perfil de renderização de GPU

A outra ferramenta com maior probabilidade de fornecer informações concretas sobre gargalos de desempenho é a Criação do perfil de renderização de GPU no dispositivo, disponível em dispositivos com Android 6.0 (API de nível 23) e versões posteriores. Essa ferramenta permite ver quanto tempo o layout e a avaliação demoram para cada frame de renderização (link em inglês). Esses dados podem ajudar você a diagnosticar problemas de desempenho no ambiente de execução e a determinar quais problemas de layout e medida você precisa solucionar, se houver.

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 o uso dessa ferramenta, consulte o Tutorial da Criação do perfil de renderização de GPU.

Lint

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

Figura 1. Localização de Inspect Code no Android Studio.

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

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

Clicar em um desses itens revela o problema associado a ele no painel à direita.

Para entender mais sobre assuntos e questões específicos nessa área, consulte a documentação de Lint.

Layout Inspector

A ferramenta Layout Inspector do Android Studio traz uma representação visual da hierarquia de visualização do seu app. Essa é uma boa maneira de navegar pela hierarquia do seu app, fornecendo uma representação visual clara da rede mãe de uma exibição específica e permitindo que você inspecione os layouts criados pelo app.

As visualizações que o Layout Inspector apresenta também podem ajudar a identificar problemas de desempenho que surgem com a tributação dupla. Ele também pode oferecer uma maneira fácil de identificar redes profundas de layouts aninhados ou áreas de layout com uma grande quantidade de filhos aninhados, outra possível fonte de custos de desempenho. Nesses casos, os estágios de layout e avaliação podem ter um custo particularmente alto, resultando em problemas de desempenho.

Para saber mais, consulte Depurar seu layout com o Layout Inspector.

Resolver problemas de hierarquia de visualização

O conceito fundamental por trás da solução de problemas de desempenho que surgem das hierarquias de visualização é simples, mas a prática é mais difícil. Impedir que hierarquias de visualização imponham penalidades de desempenho abrange os objetivos duplos de 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

Os desenvolvedores geralmente usam mais layouts aninhados do que o necessário. Por exemplo, um contêiner RelativeLayout pode apresentar um único filho que também é um contêiner RelativeLayout. Esse aninhamento resulta em uma redundância e acrescenta custos desnecessários à hierarquia de visualização.

A Lint geralmente sinaliza esse problema para você, reduzindo o tempo de depuração.

Adotar mesclagem/inclusão

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

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

E em seguida, uma tag de inclusão para adicionar esse item ao 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>
    

A inclusão aninha desnecessariamente o primeiro layout no segundo.

A tag de mesclagem pode ajudar a evitar o problema. Para saber mais sobre essa tag, consulte Reutilização de layouts com <include>.

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 sua hierarquia, alternando para um tipo de layout totalmente diferente.

Por exemplo, você pode descobrir que um TableLayout oferece a mesma funcionalidade que um layout mais complexo com várias dependências de posição. Na versão N do Android, a classe ConstraintLayout (link em inglês) oferece uma funcionalidade semelhante ao RelativeLayout, mas a um custo bem menor.