Conceitos básicos do Kotlin para Android 04.2: situações complexas de ciclo de vida

Este codelab faz parte do curso Conceitos básicos do Kotlin para Android. Você vai aproveitar mais este curso se fizer os codelabs em sequência. Todos os codelabs do curso estão listados na página inicial dos codelabs de princípios básicos do Kotlin para Android.

Introdução

No último codelab, você aprendeu sobre os ciclos de vida Activity e Fragment e conheceu os métodos chamados quando o estado do ciclo de vida muda em atividades e fragmentos. Neste codelab, você vai conhecer o ciclo de vida da atividade com mais detalhes. Você também vai aprender sobre a biblioteca de ciclo de vida do Android Jetpack, que ajuda a gerenciar eventos de ciclo de vida com um código mais organizado e fácil de manter.

O que você já precisa saber

  • O que é uma atividade e como criá-la no seu app.
  • Os conceitos básicos dos ciclos de vida de Activity e Fragment e os callbacks invocados quando uma atividade se move entre estados.
  • Como substituir os métodos de callback do ciclo de vida onCreate() e onStop() para realizar operações em diferentes momentos do ciclo de vida da atividade ou do fragmento.

O que você vai aprender

  • Como configurar, iniciar e interromper partes do app nos callbacks do ciclo de vida.
  • Como usar a biblioteca de ciclo de vida do Android para criar um observador de ciclo de vida e facilitar o gerenciamento do ciclo de vida de atividades e fragmentos.
  • Como os encerramentos de processos do Android afetam os dados no seu app e como salvar e restaurar esses dados automaticamente quando o Android fecha o app.
  • Como a rotação do dispositivo e outras mudanças de configuração criam mudanças nos estados do ciclo de vida e afetam o estado do app.

O que você aprenderá

  • Modifique o app DessertClicker para incluir uma função de timer e inicie e pare esse timer em vários momentos do ciclo de vida da atividade.
  • Modifique o app para usar a biblioteca de ciclo de vida do Android e converta a classe DessertTimer em um observador de ciclo de vida.
  • Configure e use o Android Debug Bridge (adb) para simular o encerramento do processo do app e os callbacks do ciclo de vida que ocorrem depois.
  • Implemente o método onSaveInstanceState() para reter os dados do app que podem ser perdidos se ele for fechado de forma inesperada. Como adicionar um código para restaurar esses dados quando o app for iniciado novamente.

Neste codelab, você vai expandir o app DessertClicker do codelab anterior. Você adiciona um timer em segundo plano e converte o app para usar a biblioteca de ciclo de vida do Android.

No codelab anterior, você aprendeu a observar os ciclos de vida de atividades e fragmentos substituindo vários callbacks de ciclo de vida e registrando quando o sistema invoca esses callbacks. Nesta tarefa, você vai conhecer um exemplo mais complexo de gerenciamento de tarefas do ciclo de vida no app DessertClicker. Você vai usar um timer que imprime uma instrução de registro a cada segundo, com a contagem do número de segundos em que ele está em execução.

Etapa 1: configurar o DessertTimer

  1. Abra o app DessertClicker do último codelab. (Você pode baixar o DessertClickerLogs aqui se não tiver o app.)
  2. Na visualização Project, expanda java > com.example.android.dessertclicker e abra DessertTimer.kt. No momento, todo o código está comentado, então ele não é executado como parte do app.
  3. Selecione todo o código na janela do editor. Selecione Code > Comment with Line Comment ou pressione Control+/ (Command+/ em um Mac). Esse comando remove o comentário de todo o código no arquivo. O Android Studio pode mostrar erros de referência não resolvidos até que você recrie o app.
  4. Observe que a classe DessertTimer inclui startTimer() e stopTimer(), que iniciam e interrompem o timer. Quando startTimer() está em execução, o timer imprime uma mensagem de registro a cada segundo, com a contagem total dos segundos em que o tempo está sendo executado. O método stopTimer(), por sua vez, interrompe o timer e as instruções de registro.
  1. Abra MainActivity.kt. Na parte de cima da classe, logo abaixo da variável dessertsSold, adicione uma variável para o timer:
private lateinit var dessertTimer : DessertTimer;
  1. Role a tela para baixo até onCreate() e crie um novo objeto DessertTimer logo após a chamada para setOnClickListener():
dessertTimer = DessertTimer()


Agora que você tem um objeto de timer de sobremesa, considere onde iniciar e parar o timer para que ele seja executado apenas quando a atividade estiver na tela. Você vai conferir algumas opções nas próximas etapas.

Etapa 2: iniciar e interromper o timer

O método onStart() é chamado pouco antes de a atividade ficar visível. O método onStop() é chamado depois que a atividade deixa de ficar visível. Esses callbacks parecem bons candidatos para iniciar e parar o timer.

  1. Na classe MainActivity, inicie o timer no callback onStart():
override fun onStart() {
   super.onStart()
   dessertTimer.startTimer()

   Timber.i("onStart called")
}
  1. Parar o timer em onStop():
override fun onStop() {
   super.onStop()
   dessertTimer.stopTimer()

   Timber.i("onStop Called")
}
  1. Compile e execute o app. No Android Studio, clique no painel Logcat. Na caixa de pesquisa do Logcat, digite dessertclicker, que vai filtrar as classes MainActivity e DessertTimer. Observe que, assim que o app é iniciado, o timer também começa a rodar imediatamente.
  2. Clique no botão Voltar e observe que o timer para novamente. O timer para porque a atividade e o timer que ele controla foram destruídos.
  3. Use a tela "Recentes" para voltar ao app. No Logcat, o timer é reiniciado do zero.
  4. Clique no botão Compartilhar. No Logcat, o timer ainda está em execução.

  5. Clique no botão Página inicial. No Logcat, o timer para de ser executado.
  6. Use a tela "Recentes" para voltar ao app. No Logcat, o timer começa de novo de onde parou.
  7. Em MainActivity, no método onStop(), adicione um comentário à chamada de stopTimer(). Comentar stopTimer() demonstra o caso em que você inicia uma operação em onStart(), mas se esquece de interrompê-la novamente em onStop().
  8. Compile e execute o app. Depois, clique no botão "Home" quando o timer começar. Mesmo que o app esteja em segundo plano, o timer está em execução e usando recursos do sistema continuamente. Manter o timer em execução é um vazamento de memória para o app e provavelmente não é o comportamento desejado.

    O padrão geral é que, quando você configura ou inicia algo em um callback, você interrompe ou remove esse elemento no callback correspondente. Assim, você evita que algo seja executado quando não for mais necessário.
  1. Remova a marca de comentário da linha em onStop() onde você para o timer.
  2. Recorte e cole a chamada startTimer() de onStart() para onCreate(). Essa mudança demonstra o caso em que você inicializa e inicia um recurso em onCreate(), em vez de usar onCreate() para inicializar e onStart() para iniciar.
  3. Compile e execute o app. Observe que o timer começa a ser executado, como esperado.
  4. Clique em "Home" para interromper o app. O timer para de funcionar, como esperado.
  5. Use a tela Recentes para voltar ao app. Observe que o timer não é iniciado novamente nesse caso, porque onCreate() só é chamado quando o app é iniciado, não quando ele volta ao primeiro plano.

Pontos importantes:

  • Ao configurar um recurso em um callback de ciclo de vida, também desfaça a configuração.
  • Faça a configuração e o encerramento nos métodos correspondentes.
  • Se você configurar algo em onStart(), pare ou desfaça novamente em onStop().

No app DessertClicker, é fácil perceber que, se você iniciou o timer em onStart(), precisa pará-lo em onStop(). Só há um timer, então não é difícil lembrar como interromper.

Em um app Android mais complexo, você pode configurar muitas coisas em onStart() ou onCreate() e depois desmontar tudo em onStop() ou onDestroy(). Por exemplo, você pode ter animações, músicas, sensores ou timers que precisam ser configurados e desmontados, iniciados e interrompidos. Se você esquecer um, isso vai causar bugs e dores de cabeça.

A biblioteca Lifecycle, que faz parte do Android Jetpack, simplifica essa tarefa. A biblioteca é especialmente útil quando você precisa rastrear muitas partes móveis, algumas das quais estão em diferentes estados do ciclo de vida. A biblioteca inverte a maneira como os ciclos de vida funcionam: geralmente, a atividade ou o fragmento informa a um componente (como DessertTimer) o que fazer quando ocorre um callback de ciclo de vida. Mas, ao usar a biblioteca de ciclo de vida, o próprio componente monitora as mudanças no ciclo de vida e faz o que é necessário quando elas acontecem.

Há três partes principais da biblioteca Lifecycle:

  • Proprietários do ciclo de vida, que são os componentes que têm (e, portanto, "possuem") um ciclo de vida. Activity e Fragment são proprietários de ciclo de vida. Os proprietários de ciclo de vida implementam a interface LifecycleOwner.
  • A classe Lifecycle, que contém o estado real de um proprietário de ciclo de vida e aciona eventos quando ocorrem mudanças no ciclo de vida.
  • Observadores do ciclo de vida, que observam o estado do ciclo de vida e realizam tarefas quando ele muda. Os observadores de ciclo de vida implementam a interface LifecycleObserver.

Nesta tarefa, você vai converter o app DessertClicker para usar a biblioteca de ciclo de vida do Android e aprender como ela facilita o trabalho com os ciclos de vida de atividades e fragmentos do Android.

Etapa 1: transformar o DessertTimer em um LifecycleObserver

Uma parte fundamental da biblioteca do ciclo de vida é o conceito de observação do ciclo de vida. A observação permite que classes (como DessertTimer) saibam sobre o ciclo de vida da atividade ou do fragmento e iniciem e parem por conta própria em resposta a mudanças nesses estados do ciclo de vida. Com um observador de ciclo de vida, você pode remover a responsabilidade de iniciar e interromper objetos dos métodos de atividade e fragmento.

  1. Abra a classe DesertTimer.kt.
  2. Mude a assinatura da classe DessertTimer para ficar assim:
class DessertTimer(lifecycle: Lifecycle) : LifecycleObserver {

Essa nova definição de classe faz duas coisas:

  • O construtor usa um objeto Lifecycle, que é o ciclo de vida que o timer está observando.
  • A definição de classe implementa a interface LifecycleObserver.
  1. Abaixo da variável runnable, adicione um bloco init à definição da classe. No bloco init, use o método addObserver() para conectar o objeto de ciclo de vida transmitido do proprietário (a atividade) a essa classe (o observador).
 init {
   lifecycle.addObserver(this)
}
  1. Adicione a anotação startTimer() com o @OnLifecycleEvent annotation e use o evento de ciclo de vida ON_START. Todos os eventos de ciclo de vida que seu observador pode observar estão na classe Lifecycle.Event.
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun startTimer() {
  1. Faça o mesmo com stopTimer(), usando o evento ON_STOP:
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stopTimer()

Etapa 2: modificar a MainActivity

Sua classe MainActivity já é proprietária do ciclo de vida por herança, porque a superclasse FragmentActivity implementa LifecycleOwner. Portanto, não é necessário fazer nada para tornar o ciclo de vida da atividade compatível com o ciclo de vida. Basta transmitir o objeto de ciclo de vida da atividade para o construtor DessertTimer.

  1. Abra MainActivity. No método onCreate(), modifique a inicialização de DessertTimer para incluir this.lifecycle:
dessertTimer = DessertTimer(this.lifecycle)

A propriedade lifecycle da atividade contém o objeto Lifecycle a que ela pertence.

  1. Remova a chamada para startTimer() em onCreate() e a chamada para stopTimer() em onStop(). Não é mais necessário dizer ao DessertTimer o que fazer na atividade, porque ele agora está observando o ciclo de vida e é notificado automaticamente quando o estado do ciclo de vida muda.DessertTimer Agora, tudo o que você faz nesses callbacks é registrar uma mensagem.
  2. Compile e execute o app e abra o Logcat. Observe que o timer começou a ser executado, como esperado.
  3. Clique no botão home para colocar o app em segundo plano. Observe que o timer parou de funcionar, como esperado.

O que acontece com o app e os dados dele se o Android encerrar o app enquanto ele está em segundo plano? É importante entender esse caso extremo complicado.

Quando seu app passa para o segundo plano, ele não é fechado. Ele apenas é interrompido e espera o usuário retornar. Mas uma das principais preocupações do SO Android é manter a atividade em primeiro plano funcionando sem problemas. Por exemplo, se o usuário estiver usando um app de GPS para ajudar a pegar um ônibus, é importante renderizar esse app rapidamente e continuar mostrando as direções. É menos importante manter o app DessertClicker, que o usuário pode não ter usado há alguns dias, funcionando sem problemas em segundo plano.

O Android regula os apps em segundo plano para que o apliativo em primeiro plano seja executado sem problemas. Por exemplo, o Android limita a quantidade de processamento que os apps em segundo plano podem fazer.

Às vezes, o Android encerra um processo inteiro do app, incluindo todas as atividades associadas a ele. O Android faz esse tipo de encerramento quando o sistema fica sob estresse e corre o risco de sofrer atrasos visuais. Portanto, nenhum callback ou código adicional é executado nesse momento. O processo do app é simplesmente encerrado, de forma silenciosa, em segundo plano. No entanto, para o usuário, não parece que o app foi fechado. Quando o usuário navega de volta para um app que o SO Android encerrou, o Android reinicia esse app.

Nesta tarefa, você vai simular um encerramento de processo do Android e analisar o que acontece com seu app quando ele é iniciado novamente.

Etapa 1: usar o adb para simular o encerramento de um processo

O Android Debug Bridge (adb) é uma ferramenta de linha de comando que permite enviar instruções para emuladores e dispositivos conectados ao computador. Nesta etapa, você vai usar o adb para fechar o processo do app e ver o que acontece quando o Android encerra o app.

  1. Compile e execute o app. Clique no cupcake algumas vezes.
  2. Pressione o botão home para colocar o app em segundo plano. O app será interrompido e poderá ser fechado se o Android precisar dos recursos que ele está usando.
  3. No Android Studio, clique na guia Terminal para abrir o terminal de linha de comando.
  4. Digite adb e pressione "Return".

    Se você vir muitas saídas que começam com Android Debug Bridge version X.XX.X e terminam com tags to be used by logcat (see logcat —help), está tudo certo. Se você vir adb: command not found, verifique se o comando adb está disponível no caminho de execução. Para instruções, consulte "Adicionar adb ao seu caminho de execução" no capítulo Utilitários.
  5. Copie e cole este comentário na linha de comando e pressione "Return":
adb shell am kill com.example.android.dessertclicker

Esse comando instrui todos os dispositivos ou emuladores conectados a interromper o processo com o nome do pacote dessertclicker, mas apenas se o app estiver em segundo plano. Como o app estava em segundo plano, nada aparece na tela do dispositivo ou emulador para indicar que o processo foi interrompido. No Android Studio, clique na guia Run para ver a mensagem "Application terminated". Clique na guia Logcat para ver que o callback onDestroy() nunca foi executado. Sua atividade simplesmente terminou.

  1. Use a tela "Recentes" para retornar ao app. Ele aparece nessa tela, mesmo que tenha sido colocado em segundo plano ou parado completamente. Quando você usa a tela de apps recentes para voltar ao app, a atividade é iniciada novamente. A atividade passa por todo o conjunto de callbacks do ciclo de vida de inicialização, incluindo onCreate().
  2. Quando o app é reiniciado, ele redefine a "pontuação" (o número de sobremesas vendidas e o total em dólares) para os valores padrão (0). Se o Android encerrou seu app, por que ele não salvou o estado?

    Quando o SO reinicia o app, o Android tenta redefinir o app para o estado em que ele estava antes. O Android pega o estado de algumas das suas visualizações e salva em um pacote sempre que você sai da atividade. Alguns exemplos de dados salvos automaticamente são o texto em um EditText (desde que tenha um ID definido no layout) e a pilha de retorno da sua atividade.

    No entanto, às vezes, o SO Android não conhece todos os seus dados. Por exemplo, se você tiver uma variável personalizada como revenue no app DessertClicker, o SO Android não saberá sobre esses dados nem a importância deles para sua atividade. Você precisa adicionar esses dados ao pacote por conta própria.

Etapa 2: usar o onSaveInstanceState() para salvar dados do pacote

O método onSaveInstanceState() é o callback usado para salvar todos os dados necessários caso o SO Android destrua o app. No diagrama de callback do ciclo de vida, o onSaveInstanceState() é chamado depois que a atividade é interrompida. Esse método é chamado toda vez que o app entra em segundo plano.

Pense na chamada onSaveInstanceState() como uma medida de segurança. Ela permite salvar uma pequena quantidade de informações em um pacote quando a atividade sai do primeiro plano. O sistema salva esses dados agora porque, se esperar pelo fechamento do app, poderá estar com menos recursos disponíveis. Salvar os dados todas as vezes garante que os dados atualizados no pacote estejam disponíveis para restauração, se necessário.

  1. Em MainActivity, substitua o callback onSaveInstanceState() e adicione um log statement Timber.
override fun onSaveInstanceState(outState: Bundle) {
   super.onSaveInstanceState(outState)

   Timber.i("onSaveInstanceState Called")
}
  1. Compile e execute o app. Depois, clique no botão home para colocá-lo em segundo plano. Observe que o callback onSaveInstanceState() ocorre logo após o onPause() e o onStop():
  2. Adicione estas constantes antes da definição das classes, no topo do arquivo:
const val KEY_REVENUE = "revenue_key"
const val KEY_DESSERT_SOLD = "dessert_sold_key"
const val KEY_TIMER_SECONDS = "timer_seconds_key"

Você usará essas chaves para salvar e recuperar os dados do pacote de estado da instância.

  1. Role para baixo até encontrar onSaveInstanceState() e observe o parâmetro outState, que é do tipo Bundle.

    Um pacote é um conjunto de pares de chave-valor, em que as chaves são sempre strings. É possível colocar valores primitivos, como int e boolean, no pacote.
    Como o sistema mantém esse pacote na RAM, uma prática recomendada é guardar poucos dados nele. O tamanho do pacote também é limitado, mas varia de acordo com o dispositivo. Em geral, armazene muito menos do que 100 mil. Caso contrário, seu app poderá falhar com o erro TransactionTooLargeException.
  2. No onSaveInstanceState(), coloque o valor revenue (um número inteiro) no pacote usando o método putInt():
outState.putInt(KEY_REVENUE, revenue)

O método putInt() (e métodos semelhantes da classe Bundle, como putFloat() e putString()) recebem dois argumentos: uma string para a chave (a constante KEY_REVENUE) e o valor real a ser salvo.

  1. Repita o mesmo processo com o número de sobremesas vendidas e o status do timer:
outState.putInt(KEY_DESSERT_SOLD, dessertsSold)
outState.putInt(KEY_TIMER_SECONDS, dessertTimer.secondsCount)

Etapa 3: usar onCreate() para restaurar dados de pacote

  1. Role para cima até o onCreate() e examine a assinatura do método:
override fun onCreate(savedInstanceState: Bundle) {

O método onCreate() recebe um Bundle sempre que é chamado. Quando a atividade for reiniciada devido ao encerramento de um processo, o pacote que você salvou será transmitido para o onCreate(). Se a atividade estiver sendo iniciada pela primeira vez, esse pacote em onCreate() será null. Portanto, se o pacote não for null, você saberá que está "recriando" a atividade de um ponto conhecido anteriormente.

  1. Adicione este código a onCreate(), após a configuração de DessertTimer:
if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
}

O teste de null determina se há dados no pacote ou se ele é null, o que informa se o app foi iniciado do zero ou recriado depois do encerramento. Esse teste é um padrão comum para restaurar dados do pacote.

A chave usada aqui (KEY_REVENUE) é a mesma que você usou para o putInt(). Para usar sempre a mesma chave, uma prática recomendada é definir essas chaves como constantes. Você usa o método getInt() para extrair dados do pacote, exatamente como usou o putInt() para colocar dados nele. O método getInt() recebe dois argumentos:

  • Uma string que funciona como a chave, por exemplo, "key_revenue" para o valor da receita.
  • Um valor padrão, caso não exista um para essa chave no pacote.

O número inteiro recebido do pacote é atribuído à variável revenue, e a IU usará esse valor.

  1. Adicione métodos getInt() para restaurar o número de sobremesas vendidas e o valor do timer:
if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
   dessertTimer.secondsCount =
       savedInstanceState.getInt(KEY_TIMER_SECONDS, 0)
}
  1. Compile e execute o app. Pressione o cupcake pelo menos cinco vezes até que ele mude para um donut. Clique em "Home" para colocar o app em segundo plano.
  2. Na guia Terminal do Android Studio, execute adb para encerrar o processo do app.
adb shell am kill com.example.android.dessertclicker
  1. Use a tela "Recentes" para voltar ao app. Desta vez, o app vai retornar com os valores corretos de receita e sobremesas vendidas do pacote. Mas observe que a imagem da sobremesa voltou a ser um cupcake. Só resta mais uma etapa para garantir que o app retorne do encerramento exatamente como quando foi interrompido.
  2. Na MainActivity, veja o método showCurrentDessert(). Esse método determina qual imagem de sobremesa precisa ser exibida na atividade com base no número atual de sobremesas vendidas e na lista de sobremesas da variável allDesserts.
for (dessert in allDesserts) {
   if (dessertsSold >= dessert.startProductionAmount) {
       newDessert = dessert
   }
    else break
}

Esse método depende do número de sobremesas vendidas para escolher a imagem certa. Portanto, não é necessário fazer nada para armazenar uma referência à imagem no pacote no onSaveInstanceState(). Você já está armazenando o número de sobremesas vendidas nesse pacote.

  1. No método onCreate(), no bloco que restaura o estado do pacote, chame showCurrentDessert():
 if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
   dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
   dessertTimer.secondsCount = 
      savedInstanceState.getInt(KEY_TIMER_SECONDS, 0)
   showCurrentDessert()                   
}
  1. Compile e execute o app. Depois, coloque-o em segundo plano. Use adb para encerrar o processo. Use a tela "Recentes" para voltar ao app. Agora, os valores das sobremesas vendidas, da receita total e a imagem da sobremesa foram restaurados corretamente.

Há um último caso especial no gerenciamento do ciclo de vida de atividades e fragmentos que é importante entender: como as mudanças de configuração afetam o ciclo de vida das suas atividades e fragmentos.

Uma mudança de configuração acontece quando o estado do dispositivo muda de forma tão radical que a maneira mais fácil do sistema resolver a mudança é encerrar completamente e recriar a atividade. Por exemplo, se o usuário muda o idioma do dispositivo, todo o layout pode ser alterado para acomodar diferentes direções de texto. Se o usuário conecta o dispositivo a uma base ou adiciona um teclado físico, o layout do app pode usar um layout ou tamanho de exibição diferente. E se a orientação do dispositivo mudar (se ele for girado do modo retrato para paisagem ou vice-versa), o layout poderá mudar para se ajustar à nova orientação.

Etapa 1: conhecer a rotação do dispositivo e os callbacks do ciclo de vida

  1. Compile e execute seu app, depois abra o Logcat.
  2. Gire o dispositivo ou o emulador para o modo paisagem. Você pode girar o emulador para a esquerda ou a direita usando os botões de rotação ou usando as setas e o botão Control (Command e as teclas de seta no Mac).
  3. Analise a saída no Logcat. Filtre a saída na MainActivity.
    Quando o dispositivo ou emulador gira a tela, o sistema chama todos os callbacks do ciclo de vida para encerrar a atividade. Em seguida, enquanto a atividade é recriada, o sistema chama todos os callbacks do ciclo de vida para iniciá-la.
  4. Em MainActivity, comente todo o método onSaveInstanceState().
  5. Compile e execute o app novamente. Clique no cupcake algumas vezes e gire o dispositivo ou emulador. Desta vez, quando o dispositivo é girado e a atividade é encerrada e recriada, ela é iniciada com valores padrão.

    Quando uma mudança de configuração ocorre, o Android usa o mesmo pacote de estado da instância que você aprendeu na tarefa anterior para salvar e restaurar o estado do app. Assim como em um encerramento de processo, use onSaveInstanceState() para colocar os dados do app no pacote. Em seguida, restaure os dados em onCreate() para evitar a perda de dados de estado da atividade se o dispositivo for girado.
  6. Em MainActivity, remova o comentário do método onSaveInstanceState(), execute o app, clique no cupcake e gire o app ou dispositivo. Desta vez, os dados de sobremesa são mantidos na rotação da atividade.

Projeto do Android Studio: DessertClickerFinal (link em inglês)

Dicas sobre o ciclo de vida

  • Se você configurar ou iniciar algo em um callback de ciclo de vida, pare ou remova esse item no callback correspondente. Ao parar o dispositivo, você garante que ele não continue funcionando quando não for mais necessário. Por exemplo, se você configurar um timer em onStart(), precisará pausar ou parar o timer em onStop().
  • Use onCreate() apenas para inicializar as partes do app que são executadas uma vez, quando o app é iniciado. Use onStart() para iniciar as partes do app que são executadas quando ele é iniciado e sempre que volta ao primeiro plano.

Biblioteca do ciclo de vida

  • Use a biblioteca de ciclo de vida do Android para transferir o controle do ciclo de vida da atividade ou do fragmento para o componente real que precisa ter reconhecimento do ciclo de vida.
  • Os proprietários do ciclo de vida são componentes que têm (e, portanto, "possuem") ciclos de vida, incluindo Activity e Fragment. Os proprietários de ciclo de vida implementam a interface LifecycleOwner.
  • Os observadores do ciclo de vida prestam atenção ao estado atual do ciclo de vida e realizam tarefas quando ele muda. Os observadores de ciclo de vida implementam a interface LifecycleObserver.
  • Os objetos Lifecycle contêm os estados reais do ciclo de vida e acionam eventos quando o ciclo de vida muda.

Para criar uma classe compatível com o ciclo de vida:

  • Implemente a interface LifecycleObserver em classes que precisam ser compatíveis com o ciclo de vida.
  • Inicialize uma classe observadora de ciclo de vida com o objeto de ciclo de vida da atividade ou do fragmento.
  • Na classe de observador do ciclo de vida, anote os métodos compatíveis com o ciclo de vida com a mudança de estado do ciclo de vida em que eles estão interessados.

    Por exemplo, a anotação @OnLifecycleEvent(Lifecycle.Event.ON_START) indica que o método está monitorando o evento do ciclo de vida onStart.

Encerramento de processos e salvamento do estado da atividade

  • O Android regula os apps em segundo plano para que o app em primeiro plano seja executado sem problemas. Essa regulamentação inclui limitar a quantidade de processamento que os apps em segundo plano podem fazer e, às vezes, até mesmo encerrar todo o processo do app.
  • O usuário não consegue saber se o sistema desligou um app em segundo plano. O app ainda aparece na tela de apps recentes e é reiniciado no mesmo estado em que o usuário o deixou.
  • O Android Debug Bridge (adb) é uma ferramenta de linha de comando que permite enviar instruções para emuladores e dispositivos conectados ao computador. Use adb para simular o encerramento de um processo no app.
  • Quando o Android encerra o processo do app, o método de ciclo de vida onDestroy() não é chamado. O app simplesmente para.

Como preservar o estado de atividades e fragmentos

  • Quando seu app for movido para o segundo plano, pouco depois de onStop() ser chamado, os dados do app serão salvos em um pacote. Alguns dados, como o conteúdo de um EditText, são salvos automaticamente.
  • O pacote é uma instância de Bundle, que é uma coleção de chaves e valores. As chaves são sempre strings.
  • Use o callback onSaveInstanceState() para salvar outros dados no pacote que você quer guardar, mesmo que o app tenha sido encerrado automaticamente. Para colocar dados nele, use os métodos do pacote que começam com put, como putInt().
  • Você pode acessar os dados do pacote no método onRestoreInstanceState() ou, geralmente, em onCreate(). O método onCreate() tem um parâmetro savedInstanceState que armazena o pacote.
  • Se a variável savedInstanceState contiver null, a atividade foi iniciada sem um pacote de estado e não há dados de estado a serem recuperados.
  • Para recuperar dados do pacote com uma chave, use os métodos do Bundle que começam com get, como getInt().

Mudanças de configuração

  • Uma mudança de configuração acontece quando o estado do dispositivo muda de forma tão radical que a maneira mais fácil do sistema resolver a mudança é encerrar e recriar a atividade.
  • O exemplo mais comum de uma mudança de configuração é quando o usuário gira o dispositivo do modo de retrato para o de paisagem ou vice-versa. Uma mudança de configuração também pode ocorrer quando o idioma do dispositivo mudar ou um teclado físico for conectado.
  • Quando uma mudança de configuração ocorre, o Android invoca todos os callbacks de desligamento do ciclo de vida da atividade. Em seguida, o Android reinicia a atividade do zero, executando todos os callbacks de inicialização do ciclo de vida.
  • Quando o Android encerra um app devido a uma mudança de configuração, ele reinicia a atividade usando o pacote de estado disponível para o método onCreate().
  • Como no caso do encerramento do processo, salve o estado do app no pacote no método onSaveInstanceState().

Curso da Udacity:

Documentação do desenvolvedor Android:

Outro:

Esta seção lista as possíveis atividades de dever de casa para os alunos que estão fazendo este codelab como parte de um curso ministrado por um professor. Cabe ao professor fazer o seguinte:

  • Atribuir o dever de casa, se necessário.
  • Informar aos alunos como enviar deveres de casa.
  • Atribuir nota aos deveres de casa.

Os professores podem usar essas sugestões o quanto quiserem, podendo passar os exercícios que acharem mais apropriados como dever de casa.

Se você estiver seguindo este codelab por conta própria, sinta-se à vontade para usar esses deveres de casa para testar seu conhecimento.

Mudar um app

Abra o app DiceRoller da lição 1. Baixe o app aqui se você ainda não tiver feito isso. Compile e execute o app. Observe que, se você girar o dispositivo, o valor atual dos dados será perdido. Implemente onSaveInstanceState() para reter esse valor no pacote e restaure-o em onCreate().

Responda estas perguntas

Pergunta 1

Seu app contém uma simulação física que exige cálculos complexos para ser mostrada. Em seguida, o usuário recebe uma ligação. Qual destas afirmativas é verdadeira?

  • Durante a ligação, continue calculando as posições dos objetos na simulação física.
  • Durante a ligação, pare de calcular as posições dos objetos na simulação física.

Pergunta 2

Que método do ciclo de vida precisa ser substituído para pausar a simulação quando o app não está na tela?

  • onDestroy()
  • onStop()
  • onPause()
  • onSaveInstanceState()

Pergunta 3

Para tornar uma classe compatível com o ciclo de vida usando a biblioteca Lifecycle do Android, qual interface a classe precisa implementar?

  • Lifecycle
  • LifecycleOwner
  • Lifecycle.Event
  • LifecycleObserver

Pergunta 4

Em quais circunstâncias o método onCreate() na sua atividade recebe um Bundle com dados (ou seja, o Bundle não é null)? Mais de uma resposta pode ser válida.

  • Se a atividade for reiniciada depois que o dispositivo for girado.
  • Se a atividade for iniciada do zero.
  • Se a atividade for retomada depois de retornar do segundo plano.
  • Se o dispositivo for reinicializado.

Comece a próxima lição: 5.1: ViewModel e ViewModelFactory

Para acessar links de outros codelabs neste curso, consulte a página inicial dos codelabs de conceitos básicos do Kotlin para Android.