Otimizando a Performance de Layouts em Android

Olá, galera da Comunidade! Este é o meu primeiro post aqui no blog. Desejo tratar de um assunto que pelo menos a mim era ignorado: a otimização de aplicativos para a plataforma Android. Isso é compressível, porque acabamos dando toda a nossa atenção a codificação de funcionalidades e/ou ao aspecto visual dos nossos apps. Você é iniciante em Android? Então dê uma olhada neste post que explica passo-a-passo como começar o desenvolvimento em Android.

Existem algumas outras etapas muito importantes do desenvolvimento de um bom aplicativo. Uma delas é a otimização. Porém, esta é uma atividade um tanto ignorada por quem aprendeu a programar primeiro para desktop ou para servidores, pois assumimos que os recursos computacionais gerenciados  (memória, processamento, disco, capacidade de I/O etc.) são bem maiores. No entanto, o ambiente onde o sistema operacional e a máquina virtual do Android (Dalvik / ART) normalmente roda é em dispositivos mais restritos, nos quais o consumo de recursos deve ser moderado, para não comprometer a autonomia da bateria, haver superaquecimentos ou travar devido ao excesso de utilização destes.

Por isso a otimização em dispositivos móveis é essencial, pois assim aumentamos o tempo de vida da bateria, por exemplo, já que o app não vai gastar energia do smartphone para fazer o mesmo conjunto de operações várias vezes e/ou então realiza um número delas sem que isso altere a funcionalidade em questão, basicamente essa é o significado computacional de otimização, sendo que há várias maneiras de se otimizar um aplicativo Android, nesse artigo só tratarei da otimização da renderização de layouts. Então vamos lá.

Um pouco sobre Layouts

Android possui um mecanismo de hierárquica de componentes gráficos denominados Views e ViewGroups, no qual o primeiro representam os widgets e o segundo, os layouts ou containers. Esta hierarquia permite a criação de layouts bem complexos e ricos para entregarmos mais experiência com o usuário, como mostra a imagem a seguir. Para saber mais sobre como construir layouts, veja este link.

\\\"layoutparams\\\"

A plataforma Android já disponibiliza cinco layouts (View Groups) principais: LinearLayout, GridLayout, TableLayout, RelativeLayout e FrameLayout. Cada um possui formas ligeiramente distintas de arranjar os componentes (Views ou Widgets) na tela. Mesmo assim, pode ser um pouco difícil de escolher qual layout devemos usar na exibição de uma Activity ou Fragment, devido a alguns deles poderem ter efeitos bem semelhantes sobre os componentes. Na fase de prototipagem, por exemplo, geralmente escolhemos os mais simples para validarmos logo a ideia, embora nem sempre seja melhor opção deixar assim.

Exemplo: Layout de um item de uma lista

Como exemplo, iremos construir um item de layout para listas ou coleções, que podem ser representadas por ListView, GridView ou o novo componente: RecyclerView. Esse layout é bem simples, composto pelo ícone, um título e um subtítulo, como é mostrada a imagem abaixo.

\\\"list_item\\\"

 E agora, apresento uma alternativa de construir esse layout utilizando um LinearLayout.

[code language=\\\”xml\\\”]
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:padding="6dip"
tools:context=".MainActivity">

<ImageView
android:layout_height="match_parent"
android:layout_width="wrap_content"

android:src="@mipmap/ic_launcher"
android:layout_marginRight="6dip"/>

<LinearLayout
android:layout_width="0dip"
android:layout_height="match_parent"

android:layout_weight="1"
android:orientation="vertical">

<TextView android:text="Título"
android:layout_width="match_parent"
android:layout_height="0dp"

android:layout_gravity="center_vertical"
android:textSize="18sp"
android:layout_weight="1" />

<TextView android:text="Este texto é a descrição deste item"
android:layout_width="match_parent"
android:layout_height="0dip"

android:layout_weight="1"
android:singleLine="true"
android:ellipsize="marquee" />

</LinearLayout>

</LinearLayout>
[/code]

Basicamente, é definido que o item desta lista será um LinearLayout horizontal, que contém por sua vez um ImageView, onde vai ser exibido ícone, e outro LinearLayout vertical, sendo que este último contém dois TextViews, um para o título e outro para o subtítulo.  A abordagem acima funciona, mas não é a melhor forma, haja vista que o custo de instanciar e renderizar essa configuração de layout pode crescer drasticamente, uma vez que o LinearLayout utiliza peso (android:layout_weight) para dispor os elementos, necessitando assim cálculos extras para renderizá-los de acordo com a proporção definida no peso.

Otimizando layouts

Agora vamos utilizar a mesma ideia, porém utilizando um RelativeLayout para dispor os componentes e obter o mesmo efeito visual.

[code language=\\\”xml\\\”]
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:padding="6dip"
tools:context=".MainActivity"
tools:ignore="RtlHardcoded">

<ImageView
android:id="@+id/icone"

android:layout_width="wrap_content"
android:layout_height="match_parent"

android:layout_alignParentTop="true"
android:layout_alignParentBottom="true"
android:layout_marginRight="6dip"

android:src="@mipmap/ic_launcher" />

<TextView
android:id="@+id/subtitulo"

android:layout_width="match_parent"
android:layout_height="26dip"

android:layout_toRightOf="@id/icone"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"

android:singleLine="true"
android:ellipsize="marquee"
android:text="Este texto é a descrição deste item"
/>

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"

android:layout_toRightOf="@id/icone"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_above="@id/subtitulo"
android:layout_alignWithParentIfMissing="true"

android:gravity="center_vertical"
android:textSize="18sp"
android:text="Título" />

</RelativeLayout>
[/code]

Explicando o código XML, temos um layout escrito de uma maneira bem diferente, de início podemos perceber que em vez do LinearLayout vertical do ViewGroup principal temos um RelativeLayout, e que o LinearLayout vertical também sumiu, estando agora os dois TextViews ligados diretamente, cada um com seus atributos relativos. Aí que alguém pode se perguntar: se o visual é o mesmo então qual a diferença? Bom, há muita diferença, mas primeiro devo esclarecer como o RelativeLayout funciona.

Os componentes estão todos dentro mesmo ViewGroup, e esse é relativo, o que significa que um os componentes se agrupam em posições relativas uns outros ou ao container pai. Primeiro, declara-se a imagem alinhada à esquerda por padrão, ao topo (android:layout_alignParentTop), abaixo (android:layout_alignParentBottom) e que esta ocupará toda a altura do item na lista (android:layout_height=\\\”match_parent\\\”). Depois, declara-se o TextView do subtítulo alinhado à direita (android:layout_alignParentRight) do container e também relativo à direita da imagem anterior (android:layout_toRightOf). Para finalizar, temos o segundo TextView alinhado à direita da mesma imagem e acima da segunda linha (android:layout_above).

Pode ser um pouco mais complexo e não sequencial ter que gerenciar todas essas relações, mas é uma grande vantagem como veremos a seguir.

A ferramenta Hierarchy Viewer

O Hierarchy Viewer é uma ferramenta muito boa que exibe a hierarquia de um layout, que já vem no SDK e acessível tanto pelo Android Studio quanto pelo ADT.  Com ele é possível inspecionar a árvore de componentes de toda a aplicação e visualizar onde está o gargalo, isto é, qual parte do layout que pode vir a diminuir a performance da sua app. Siga esses seguintes passos para utilizar a ferramenta:

  1. Verifique se seu dispositivo está conectado ou existe uma instância do emulador carregada. Por questão de segurança, o Hierarchy Viewer só irá carregar em dispositivos que estiverem executando uma versão de desenvolvimento da plataforma Android
  2. Execute a aplicação no dispositivo
  3. Garanta que o layout que você quer inspecionar está visível na tela
  4. Para carregar o Hierarchy Viewer, no Android Studio, aponte para Tools > Android > Android Device Monitor ou digite na linha de comando e pressione ENTER:
    $> hierarchyviewer
  5. Na tela que abrir, expanda a seta ao lado do dispositivo e selecione a Activity relacionada ao layout que quer inspecionar
  6. Clique em Load View Hierarchy e aguarde a hierarquia de layout ser visualizada

Inspecionando os layouts com o Hierarchy Viewer, obtemos o seguinte resultado apresentado nas imagens abaixo. Como você pode observar, existem três círculos coloridos: vermelho, informando que aquele layout está demorando muito para ser carregado; amarelo, indicando que o carregamento está demorando moderadamente; e verde, indicando que aquela porção do layout está otimizada e provavelmente não irá influenciar na performance.

\\\"hierarchy-linearlayout\\\" \\\"hierarchy-relativelayout\\\"

Analisando o algoritmo utilizado pelo renderizador, por exempo na hora que você estiver inflando um layout por meio de um LayoutInflater ou apenas chamando o método setContentView() da sua Activity, basicamente ele irá fazer o seguinte:

  • Percorrer cada ponto da árvore de layout, como se fosse um labirinto, ele vai passar por cada caminho possível (a menos quando se diz para não fazer, não inflando o layout ou configurando a visibilidade como GONE, o que na prática faz que tal caminho não exista ou esteja bloqueado, respectivamente).
  • A cada ida e vinda é mais tempo e energia gasto, obviamente. Apontando que ele não pode pular de um nó vizinho a outro, então ele sempre volta ao pai e e vai para o nó imediamente a direita até o último nó mais à direita do nível profundo, acessível. Fazendo as contas para percorrer o primeiro foram 7 passos e no segundo mais otimizado, foram apenas cinco.

Pode não parecer muito mais em layout mais complexos a redução será bem maior, principalmente ao considerar fazer isso em todas as suas telas. Na hora de instanciar (realizar o dimensionamento, organizar o layout e desenhar) em vez de cinco componentes temos quatro. Menos uso memória para armazenar os estados e processamento gráfico para dimensionar/desenhar esses componentes.

Resultados de performance

Eis uma tabela que mostra o comparativo no tempo de renderização utilizando a primeira abordagem com LinearLayout e a segunda otimizada, com RelativeLayout.

TEMPO DE RENDERIZAÇÃO
Operações LinearLayout (ms) RelativeLayout (ms)
Dimensionamento 0.977 0.598
Layout 0.167 0.110
Desenho 2.717 2.146

Bem, foi interessante pesquisar sobre isso para escrever esse artigo que pode ainda ser complementado no futuro, pois há muito a se explorar a respeito dos layouts e também da área de otimização na plataforma Android.

E você já enfrentou problemas de performance nas suas apps ou layouts? Deixe seu comentário aqui contando como resolveu.

Abraços e até a próxima! =D

Referências:

http://android-developers.blogspot.com.br/2009/02/android-layout-tricks-1.html

http://developer.android.com/training/improving-layouts/optimizing-layout.html

https://www.udacity.com/course/android-performance–ud825

http://developer.android.com/guide/topics/ui/layout/linear.html

http://developer.android.com/guide/topics/ui/layout/relative.html


Comentários

Uma resposta para “Otimizando a Performance de Layouts em Android”

  1. Muito bom seu artigo Thiago.
    Simples e com detalhes interessantissímos!

    Obrigado por compartilhar seu conhecimento!

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *