Tags Merge e ViewStub Para Otimizar Sua APP Android

Receba em primeira mão, e com prioridade, os conteúdos Android exclusivos do Blog. Você receberá um email de confirmação. Somente depois de confirma-lo é que poderei lhe enviar os conteúdos exclusivos.

Email inválido.
Blog /Android /Tags Merge e ViewStub Para Otimizar Sua APP Android

Tags Merge e ViewStub Para Otimizar Sua APP Android

Vinícius Thiengo
(2408) (6)
Go-ahead
"Teoria é esplêndida, mas até colocar em prática, é sem valor."
James Cash Penney
Kotlin Android
Capa do livro Desenvolvedor Kotlin Android - Bibliotecas para o dia a dia
TítuloDesenvolvedor Kotlin Android - Bibliotecas para o dia a dia
CategoriasAndroid, Kotlin
AutorVinícius Thiengo
Edição
Capítulos19
Páginas1035
Acessar Livro
Treinamento Oficial
Android: Prototipagem Profissional de Aplicativos
CursoAndroid: Prototipagem Profissional de Aplicativos
CategoriaAndroid
InstrutorVinícius Thiengo
NívelTodos os níveis
Vídeo aulas186
PlataformaUdemy
Acessar Curso
Receitas Android
Capa do livro Receitas Para Desenvolvedores Android
TítuloReceitas Para Desenvolvedores Android
CategoriaDesenvolvimento Android
AutorVinícius Thiengo
Edição
Ano2017
Capítulos20
Páginas936
Acessar Livro
Código Limpo
Capa do livro Refatorando Para Programas Limpos
TítuloRefatorando Para Programas Limpos
CategoriaEngenharia de Software
AutorVinícius Thiengo
Edição
Capítulos46
Páginas599
Acessar Livro
Quer aprender a programar para Android? Acesse abaixo o curso gratuito no Blog.
Conteúdo Exclusivo
Receba em primeira mão, e com prioridade, os conteúdos Android exclusivos do Blog.
Email inválido

Tudo bem?

Neste artigo vamos estudar duas maneiras de otimizar o uso de layouts em aplicativos Android. Uma delas é mais "uma correção" do que uma otimização, no caso, a tag <merge>.

Antes que você pense que ambas as entidades apresentadas aqui (<merge> e <ViewStub>) são novas, na verdade não são.

Tanto que os dois melhores conteúdos que encontrei dessas tags XML são do blog do Android, mais precisamente estão entre os artigos de Março de 2009.

Note que mesmo essas sendo tags old school, elas são muito úteis até hoje (e muito provavelmente continuarão sendo).

Antes de prosseguir, não esqueça de se inscrever 📫 na lista de e-mails do Blog para receber semanalmente as novidades do desenvolvimento Android, novidades liberadas em primeira mão para os inscritos na mailing list.

Abaixo seguem os tópicos que estaremos abordando nesse artigo:

O problema de não otimizar o uso de memória

Já faz alguns muitos artigos e vídeos aqui do Blog em que falamos sobre a necessidade de otimizarmos sempre o código de nossos aplicativos Android com o objetivo de evitarmos vazamento de memória.

Há vários caminhos possíveis para que a memória seja utilizada de forma a aproximar o aplicativo ainda mais da exceção OutOfMemoryException. Na documentação do Android há algumas páginas mostrando aos programadores como evitar essa Exception.

Quando seu aplicativo utiliza imagens, áudios ou vídeos o problema fica ainda mais sério, pois essas mídias consomem muitos dos recursos disponíveis ao app.

Logo, diferente do que é para softwares Web ou Desktop, qualquer conteúdo sobre otimização de código mobile, mais especificamente sobre código Android, em minha opinião, você deve parar e ler artigo e comentários.

Comentários também?

Sim. Muitas vezes são os comentários que respondem muitas de nossas dúvidas ou até mesmo complementam o conteúdo com dicas valiosas.

Aqui no Blog, sempre que indico algum artigo para programadores com dúvidas, solicito que também leiam os comentários.

Mas enfim... Como o AppJolt nos alertou com a apresentação da API de anúncios dele: quatro de cada cinco aplicativos instalados em aparelhos Android são removidas logo em seguida.

Evitar um OutOfMemoryException aumenta nossas chances de permanecer com nossos aplicativos nos devices Android.

Seguindo o conteúdo vamos falar sobre técnicas (tags) que trazem ao menos dois benefícios, não somente o de evitar o uso indevido da memória, mas também o que permite que o usuário do aplicativo veja esta carregando e respondendo aos comandos dele de maneira eficiente.

Projeto de exemplo

O projeto utilizado no exemplo do artigo pode ser encontrado na seguinte página no GitHub: https://github.com/viniciusthiengo/merge-viewstub-project

Tela principal do projeto Android de exemplo

Se estiver acompanhando o Blog a algum tempo notará que o projeto é quase o mesmo do utilizado no artigo da API da In Loco Media. No projeto presente aqui nós removemos essa API de anúncios.

Para melhor aproveitar o conteúdo apresentado no decorrer do post, recomendo que baixe o projeto e o abra em seu Android Studio.

Pois para simplificar e focar nas apresentações das tags <merge> e <ViewStub>, vou colocar somente os códigos Java e XML diretamente relacionados a elas.

Trabalhando com o <merge>

Acessando o projeto no Android Studio e indo ao layout app_bar_main.xml tínhamos a seguinte configuração:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="br.com.thiengo.geolocationads.MainActivity">

<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />

</android.support.design.widget.AppBarLayout>

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

<FrameLayout
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:background="#44ffffff">

<ProgressBar
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_gravity="center" />
</FrameLayout>

</android.support.design.widget.CoordinatorLayout>

 

Esse layout é carregado dentro do layout principal da MainActivity via tag <include>.

O que nos interessa no código acima, nessa seção, é o layout content_main.xml que está referenciado na tag <include>.

Esse layout, content_main.xml, antes das atualizações no projeto que está no GitHub, tinha o seguinte código XML:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/content_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="br.com.thiengo.geolocationads.MainActivity"
tools:showIn="@layout/app_bar_main"
android:orientation="vertical">

<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
</LinearLayout>

 

Esse é o código remanescente de quando era inserida, via Java API, uma AdView para anúncios de alguma API de monetização. Agora, sem essa característica, View de anúncios, esse layout está pouco eficiente, pois:

  • O ViewRoot <LinearLayout> não precisa mais ser utilizado, pois os atributos de design nele, que é o que ainda importa para nós, podem ser configurados na tag <ListView>;
  • Os atributos android:layout_height="0"android:layout_weight="1" somente forçam um duplo processamento para a inclusão da visualização ListView no layout. Esses atributos não mais são necessários, não com esses valores.

Abaixo o diagrama que representa parte de como está a configuração do layout da MainActivity depois de renderizado: 

Diagrama de renderização do layout da MainActivity

Remover o LinearLayout, sem modificar o design atual, é o nosso objetivo.

Uma das opções de melhoria para o layout content_main.xml é a utilização da tag <merge>, pois podemos continuar reaproveitando o ListView, em outros layouts, caso necessário, com as definições de design corretas e não mais estaremos utilizando uma ViewRoot desnecessária que somente ocupa mais espaço na memória reservada ao aplicativo aumentando o tempo de renderização do layout.

Abaixo o código atualizado de content_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">

<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</merge>

 

O que era necessário reaproveitar da tag <LinearLayout> para manter o mesmo design na tela do usuário nós colocamos na tag <ListView>.

A tag <merge> não deve receber nada além dos atributos de namespace que serão utilizados no layout.

Ok, mas ainda temos uma tag root, <merge>. Qual a real melhoria nesse caso?

Na verdade a tag <merge> não é uma tag root na renderização, pois o Android ignora ela e insere diretamente, no local do include de content_main.xml, as tags filhas dela. Em nosso caso, a tag <ListView> apenas.

Diferente da tag <ScrollView>, a tag <merge> não tem limites quanto ao número de tags filhas. A limitação está mesmo na utilização dela.

Além de não poder ser carregada na tag <ViewStub>, ela também não pode ser utilizada em qualquer outra parte do layout senão como tag root dele.

Abaixo a figura de nossa nova hierarquia de Views, bem, parte da nova hierarquia:

Diagrama da hierarquia de Views do layout da MainActivity

Mesmo que o ganho na velocidade de renderização seja em milissegundos, há o ganho e o uso da memória é melhorado, pois foi removida a View LinearLayout e também o uso do atributo android:layout_weight

Acessando no Android Studio "Tools" > "Android" > "Android Device Monitor", então clicando na aba superior direita "Hierarchy View" e logo em seguida, no lado esquerdo, na aba "Windows" clicando no package de nosso projeto.

Fazendo isso conseguimos visualizar que depois da adição de <merge> ListView se tornou uma tag filha direta de CoordinatorLayout:

Ferramenta Hierarchy View no Android Studio IDE

Com isso podemos prosseguir para nossa última melhoria, a utilização da tag <ViewStub>

Trabalhando com o <ViewStub>

A ViewStub é muito similar a tag <include>, porém com uma característica que permite melhorar a inicialização do layout que a contém: carregamento de conteúdo somente sob demanda.

Antes da atualização do projeto que está no GitHub, tínhamos o seguinte código no layout app_bar_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="br.com.thiengo.geolocationads.MainActivity">

...
<FrameLayout
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:background="#44ffffff">

<ProgressBar
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_gravity="center" />
</FrameLayout>
</android.support.design.widget.CoordinatorLayout>

 

Os "..." representam uma série de Views até chegarmos ao FrameLayout. O FrameLayout com o ProgressBar é utilizado para travar a ação de clique do usuário nos itens do ListView quando novos conteúdos estiverem sendo carregados.

Além de passar para o usuário a indicação desse novo carregamento. Ou seja, o FrameLayout somente fica ativo quando a ação do usuário aciona um novo carregamento, a ação de scroll down:

FrameLayout ativo na tela principal do aplicativo de exemplo

Para Views que não são necessárias no layout logo na inicialização dele, nós podemos utilizar o ViewStub para carregarmos essas Views, layouts, somente quando requisitados.

Dessa forma conseguimos acelerar, para o usuário, a apresentação do layout inicial, pois a utilização do ViewStub no local de um conjunto de Views é muito mais leve.

Nosso objetivo com o ViewStub no projeto de exemplo é chegar à seguinte hierarquia de Views:

Diagrama da hierarquia de Views com ViewStub no layout principal do projeto de exemplo

Antes de prosseguir com a atualização dos arquivos XML, vamos visualizar o método na MainActivity, loadProgressBar(), que permite a mudança de estado do FrameLayout

...
public void loadProgressBar( boolean status ){

int visibility = status ? View.VISIBLE : View.GONE;

findViewById( R.id.progress_bar ).setVisibility( visibility );
}
...

 

Aqui não é importante apresentar onde esse método é chamado, pois as invocações a loadProgressBar() serão sempre as mesmas, o conteúdo desse método é que será atualizado.

Vamos começar criando um novo layout, progress_bar.xml:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#44ffffff">

<ProgressBar
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_gravity="center" />
</FrameLayout>

 

Note que da configuração anterior para FrameLayout somente removemos o atributo android:visibility, pois esse não deve mais ser utilizado. Vamos controlar a visibilidade dessa View somente via Java API.

Voltando ao código de app_bar_main.xml, trocamos a tag FrameLayout pela tag ViewStub:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="br.com.thiengo.geolocationads.MainActivity">

...
<ViewStub
android:id="@+id/vs_progress_bar"
android:inflatedId="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout="@layout/progress_bar" />

</android.support.design.widget.CoordinatorLayout>

 

Os atributos android:layout* já apresentados na tag <ViewStub> acima são todos obrigatórios.

O atributo android:id nos permite um acesso trivial a tag <ViewStub>. O atributo android:inflatedId nos permite sobrescrever o id do layout que será carregado, em nosso caso, o layout progress_bar.xml.

Segue o código do método loadProgressBar(), atualizado:

...
public void loadProgressBar( boolean status ){

try{
int visibility = status ? View.VISIBLE : View.GONE;

findViewById( R.id.progress_bar ).setVisibility( visibility );
}
catch( Exception e ){
((ViewStub) findViewById( R.id.vs_progress_bar ) ).inflate();
}
}
...

 

A lógica de negócio utilizada foi:

  1. Verifique se é para apresentar ou esconder a View root responsável pelo ProgressBar;
  2. Aplique o novo estado de visualização a View root do ProgressBar;
  3. Caso essa View não exista no layout, acesse a visualização ViewStub e invoque o método inflate(), logo depois da aplicação do cast.

Note que logo depois de acessarmos o ViewStub e invocarmos o método inflate() ou o método setVisibility(), o ViewStub é removido da hierarquia de Views do layout, a partir desse ponto devemos acessar somente o layout carregado pelo ViewStub.

Observação: O método inflate() retorna o objeto root do layout carregado pelo ViewStub.

Com essa última atualização, podemos rodar a aplicação no emulador e então, antes de realizar o scroll down, acessar o "Hierarchy View", como fizemos na seção anterior, para verificar se nosso objetivo foi alcançado:

Hierarchy View sendo utilizado para visualizar a hierarquia do layout principal

Period! Com isso deixamos o carregamento inicial do layout app_bar_main.xml ainda mais eficiente.

Antes de prosseguir, não esqueça de se inscrever na 📫 lista de e-mails para receber, em primeira mão, os conteúdos Android exclusivos do Blog.

Se inscreva também no canal do Blog em YouTube Thiengo.

Conteúdos complementares

Obviamente que os links em Fontes vão lhe ajudar a se aprofundar no assunto: Otimização de aplicativos Android. Mas abaixo deixo alguns links, alguns também com vídeo, que vão lhe ajudar a obter ainda mais de seus códigos Java Android:

Vídeo com implementação passo a passo do exemplo

No vídeo a seguir mostro passo a passo a implementação das tags <merge> e <ViewStub>, como no exemplo do artigo:

Como já informado no início do conteúdo, para ter acesso completo ao código do projeto entre no seguinte GitHub: https://github.com/viniciusthiengo/merge-viewstub-project.

Conclusão

As tags <merge> e <ViewStub> são bem simples de utilizar e podem nos dar ganhos consideráveis em termos de melhora de desempenho de aplicativos Android.

O ponto negativo fica com a necessidade de destrinchar o código a olho nu para saber quando utiliza-las e então otimizar os layouts sempre que possível. O Lint pode ajudar, mas no caso do ViewStub, provavelmente não muito.

Não deixe de utilizar as ferramentas "Hierarchy View" e "Layout Inspector" para ajudar na busca de melhorias e também para certificar que sua nova configuração de layout, com as tags discutidas aqui, está sendo seguida.

Não deixe de utilizar as tags <merge> e <ViewStub> sempre que enxergada a oportunidade, mesmo a otimização não sendo sua prioridade inicial.

Ainda há outros conteúdos muito próximos as tags utilizadas aqui e que melhoram ainda mais a performance do aplicativo, um deles é o WeakReference que será abordado em artigos futuros aqui no blog.

Curtiu o conteúdo? Não esqueça de compartilha-lo. E, por fim, de se inscrever na 📩 lista de e-mails, respondo às suas dúvidas também por lá.

Abraço.

Fontes

Avoiding memory leaks

Re-using Layouts with <include/>

Loading Views On Demand

Android Layout Tricks #3: Optimize by merging

Android Layout Tricks #3: Optimize with stubs

Android ListView setOnScrollListener

Receba em primeira mão, e com prioridade, os conteúdos Android exclusivos do Blog.
Email inválido

Relacionado

ConstraintLayout, Melhor Performance no AndroidConstraintLayout, Melhor Performance no AndroidAndroid
Scrum - A Arte de Fazer o Trabalho na Metade do TempoScrum - A Arte de Fazer o Trabalho na Metade do TempoLivros
Proguard AndroidProguard AndroidAndroid
Lint Tool Para Alta Performance em APPs AndroidLint Tool Para Alta Performance em APPs AndroidAndroid

Compartilhar

Comentários Facebook

Comentários Blog (6)

Para código / script, coloque entre [code] e [/code] para receber marcação especifica.
Forneça seu nome válido.
Forneça seu email válido.
Forneça o comentário.
Enviando, aguarde...
01/04/2017
Professor porque ao inserir uma outra atividade da erro nesse projeto?
Pena que não dá para carregar imagem para tu ver!

Error:(39, 0) Could not get unknown property 'compile' for object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.
Responder
Vinícius Thiengo (0) (0)
01/04/2017
Alan, tudo bem?

Se possível, descreva passo a passo o que você está fazendo até o erro ser gerado.

Sobre a imagem / print, você pode coloca-la em um servidor online gratuito de imagens, até mesmo no Google Plus, e assim compartilhar o link dela aqui. Abraço.
Responder
01/04/2017
O erro foi este professor!
https://drive.google.com/file/d/0B6RKmeUX9iJQbExlRUJDVy1NVmM/view?usp=sharing

Descrição até chegar no erro: Apenas fui em File/New/Activty/Empty Activity.
Fui criar uma nova activity para colocar uma webView e o erro apareceu.
Responder
Vinícius Thiengo (0) (0)
01/04/2017
Alan, a princípio o problema está no Gradle, ou o App Level ou o Project Level.

Se possível, coloque-os no PasteBin (https://pastebin.com/ ) e compartilhe aqui o link do paste, assim vejo o que pode ser.

Coloque o print dos grades também, para ver se há as linhas vermelhas indicando problemas de referência.

Abraço.
Responder
01/04/2017
Professor não dei conta do Patebin não me perdoe, estou compartilhando continuação do seu projeto com erro pelo google drive:

https://drive.google.com/file/d/0B6RKmeUX9iJQR0NlZWdya0tDaVU/view?usp=sharing

Se deu certo me avise por favor!!! Muito obrigado por sua atenção e dispor de seu precioso tempo para compartilhar seus conhecimentos conosco.
Responder
Vinícius Thiengo (0) (0)
08/04/2017
Alan, tudo bem?

Seu problema na verdade é um pequeno bug que veio junto a nova versão do Android Studio.

Os Gradle App Level fica desorganizado algumas vezes. O seu estava assim:

https://drive.google.com/file/d/0B0fyTi4aZKXNY2RRRTdHd3NkbFE/view

Por isso teve problemas. Ele deveria estar assim:

https://drive.google.com/file/d/0B0fyTi4aZKXNQlJHWXBKQnk1M2c/view

Assim ele funciona. Faça essa alteração em seu projeto. Abraço.
Responder