Android View: ciclo de vida

Lucas
Android Dev BR
Published in
7 min readAug 2, 2019

Segundo artigo da série sobre view que fala sobre como ela se comporta em diferentes situações.

Não se preocupe, esse artigo não irá apresentar mais um diagrama de um ciclo de vida todo complicado e difícil de entender. Mas sim mostrar como uma view se comporta em determinadas situações.

Antes de tudo… 🛂

Antes de nos aprofundarmos mais a respeito do ciclo de vida dela, vale conhecemos um pouco como o Android apresenta o layout da nossa Activity para o usuário, ou seja, vamos entender superficialmente, o que o setContentView faz ou pelo menos o que acontece quando ele é chamado.

Para isso, iremos depurar o nosso layout usando a ferramenta Layout Inspector que nos ajuda a visualizar como está sendo criada a nossa hierarquia de views.

Hierarquia de um layout padrão, sem nenhum conteúdo

No XML desse exemplo fora definido apenas um LinearLayout e dentro dele há uma view chamada SomeCircleView, criada apenas para essa apresentação.

Entretanto, o Layout Inspector não mostra apenas esses 2 componentes, mas vários outros, como DecorView e LinearLayout, e dentro desse mais alguns.

Essa representação é interessante porque nos mostra que o Android não adiciona só o nosso layout, existem algumas outros componentes essenciais que também são adicionados.

Window e suas peripécias 🧐

A Window (de android.view), presente em uma Activity, é utilizada para desenhar a nossa interface na tela ou se você preferir, o nosso layout.

Quando a Activity adiciona o nosso layout (com o setContentView), o que acontece é que todas as views são removidas e o novo layout é adicionado, é por isso que você pode chamar esse método mais de uma vez, mas eu espero que você não faça isso, porque há um efeito cascata: remove tudo e redesenha tudo.

Os eventos primários… 🌜

Código simples mostrando os eventos básicos disponíveis para uma View

OnAttachedToWindow
Você provavelmente já deve saber o que significa esse evento. Caso não, quando a view for adicionada à Window, esse método é chamado, e ele só é chamado uma vez (por ciclo).

Esse método será chamado novamente se a Activity for destruída e posteriormente reconstruída, isso geralmente ocorre em um process death.

A TextView, por exemplo, usa esse evento para adicionar um listener ao evento de OnPreDraw.

OnDetachedFromWindow
Pelo próprio nome do evento, não é tão difícil de imaginar o que ele indica. Isso, resumidamente, significa que a View foi removida da Window por algum motivo, seja pela Activity ter sido destruída ou por um process death — que faz com que a Activity seja destruída.

Voltando à TextView, ela o utiliza para remover o listener que fora adicionado, evitando possíveis vazamentos de memória.

E os principais (ou Traversals)🌝

Código com adição dos eventos Traversals

Você pode estar estranhando essa palavra em Inglês no título, é, eu também a estranhei no ínicio, mas conceituamente ela significa o seguinte (nesse nosso contexto, veja o conceito computacional): cada evento exibido abaixo é recursivo.

Por exemplo, se utilizarmos o método requestLayout() em um LinearLayout, isso vai fazer com que o tamanho dele seja medido novamente (onMeasure) e depois de todas as suas views, e também vai fazer com que elas sejam desenhadas novamente.

E se dentro desse LinearLayout a gente tem outro LinearLayout com várias views, ele será afetado e suas views também, e é exatamente por esse motivo que devemos evitar de criar layouts com tanta profundidade (vários ViewGroups dentro de outros).

Ah, evite chamar esse método porque ele é bem impactante e não deve ser chamado sem necessidade, só quando você precisar que sua view(seja ela um container ou não) tenha o seu tamanho alterado.

onMeasure

Esse método é utilizado especifícamente para medir a largura e altura da sua View em um plano 2D.

Na imagem anterior nós estamos chamando método da super classe com o super.onMeasure() e isso diz o seguinte para ela: oh, resolve isso você, eu não vou medir nada não 🤦‍♂.

Hierarquia de um layout com a adição de uma View circular e sua representação na tela

E como podemos ver pelo Layout Inspector, isso não vai funcionar muito bem, já que nós foi medido um espaço enorme para um círculo de 32px.

Esse método serve simplesmente para dizer o quão grande a View vai ser e quais serão as suas dimensões (width e height), vale sempre levar em conta o padding que é um espaço aplicado dentro dela.

onLayout 👨‍👩‍👧‍👧

Esse método simplesmente serve para dizer onde as views serão posicionadas… Sim, no plural! Esse método só lhe será útil quando você estiver criando ViewGroups, porque ele vai precisar posicionar suas views em algum lugar, e esse método passa alguns parâmetros importantes para você.

onDraw 🎨

Aqui é onde a mágica ocorre. Basicamente você tem acesso ao Canvas e pode desenhar o que você quiser nele. É onde a BottomAppBar foi desenhada, por exemplo. Aquelas curvas não são meras imagens feitos em um 9-patch, mas sim um desenho bem mais complexo.

A API do Canvas é bem simples até, e seus métodos são bem descritivos no que eles fazem.

Código com exemplo de como podemos utilizar a API do Canvas

Bom, o código acima simplesmente está desenhando um círculo no Canvas. Com esse método do Canvas, ele recebe alguns parâmetros que são: coordenada x, coordenada y, raio do círculo e uma instância do Paint. E ele também leva em conta o padding que a view possui, se caso houver algum definido no XML ou por código mesmo.

Mas o código acima tem erro grotesco: sabe qual é? sei que sim.

Isso, uma instância do Paint está sendo criada dentro do onDraw, e isso é algo horrível de se fazer. Na verdade, nenhum objeto deve ser alocado em nenhum dos métodos exibidos acima, porque eles são chamados com certa frequência, principalmente o onDraw.

o onMeasure por exemplo, é chamado quando há uma necessidade de alterar o tamanho da view, o onDraw não, ele é chamado quando há uma alteração visual mais simples, por exemplo, trocar a cor de um texto faz uma chamada ao seguinte método: invalidate() que faz com que a view seja desenhada novamente.

Não devemos de maneira alguma alocar objetos nesses métodos, pois views podem ser redesenhadas a qualquer momento.

Salvando estados 🚔

Representação de estados de Views sendo mantidos

O dispositivo acima está simulando um evento de process death: assim que o usuário sai da Activity, ela é destruída. O objetivo é mostrar como o estado da nossa view pode ser salvo mesmo após o evento citado.

Ah, vale lembrar que o estado só poderá ser salvo se caso a view tiver um ID, isso pode ser melhor visualizado em EditTexts — digite um texto em uma que não tenha ID, simule o process death, volte para a Activity, o estado não será mantido.

Representação de estado de uma View que não é mantido

E como salvar esses estados?

Temos dois métodos que nos permite as seguintes ações: salvar um estado e recuperar.

  • onSaveInstanceState()
  • onRestoreInstanceState(Parcelable state)

Os métodos são conhecidos por também estarem presentes em Activities, mas lá eles armazenam Bundle, que mapeiam um conjunto de Parcelables. Em views, apenas uma Parcelable é armazenada, e não um Bundle.

Código de exemplo de o que precisamos criar para posteriormente salvar e recuperar o estado de uma View

Se você já trabalhou com Parcelables, não vai ser tão difícel de entender a razão de criar a classe acima. Mas ela serve apenas para salvar estados da nossa view.

Por exemplo, uma TextView tem sua classe de State também e ela vai armazenar o conteúdo de texto que ela exibe, assim como outros dados que precisam ser mantidos.

Código completo com salvamento e recuperação de estado

onSaveInstanceState
Simplesmente salva a instância atual da nossa view. No exemplo acima, nós salvamos a cor atual dela, que é alterada pelo método withColor().

Lembra que o invalidate() faz com que a view seja desenhada novamente? então, é ele quem cria esse efeito de alteração de cor nos vídeos anteriores.

onRestoreInstanceState
Ele é chamado quando a instância da view for restaurada. Por exemplo, num evento de process death, esse método será chamado se caso o usuário voltar novamente para o app e a activity for recriada.

Ele traz consigo um state, que podemos verificar se é do tipo do state que criamos. Se for, recuperamos o valor que desejamos e nem precisamos nos preocupar com mais nada, a view será desenhada novamente com o estado anterior antes de ser destruída.

Conclusão 🙈

Esse artigo foi um pouco mais além que o anterior, mas não tanto, como você pode ver, alguns códigos não foram apresentados (onMeasure, por exemplo), por mais que ocorreu uma explicação do método.

Mas o objetivo era esse mesmo, passar um pouco por cima dele e explicar, sem se aprofundar tanto, porque o próximo artigo falará mais sobre ele, e começará a parte de criação avançada de um componente.

Essa é uma série que pode ser bem extensa, então o ideal é que os artigos não fiquem tão longos porque pode prejudicar na leitura. De qualquer forma, acredito que deu pra compartilhar bem alguns conceitos chaves que iremos rever a posteriori.

Ah, um fato interessante é que eu consegui entender bem mais a necessidade do onSaveInstanceState e onRestoreInstanceState quando comecei a estudar sobre custom views, em Activities em sempre fui meio receoso e os achava estranho — talvez porque eu nunca me aprofundei o suficiente.

Referências
Esse artigo é fortemente inspirado na comunidade e em desenvolvedores notáveis como: Romain Guy e Ian NI-Lewis.

Se você encontrou algum problema no artigo ou tem alguma dúvida, você pode perguntar no próprio Medium ou falar comigo em alguma das minhas redes sociais abaixo.

Twitter: @coreydevv
LinkedIn: Lucas Cavalcante

Android View will return.

--

--