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ê aproveitará mais o curso se fizer os codelabs em sequência. Todos os codelabs do curso estão listados na página de destino dos codelabs do curso Conceitos básicos do Kotlin para Android.

Introdução

No último codelab, você aprendeu sobre os ciclos de vida de Activity e Fragment e explorou os métodos chamados quando o estado do ciclo de vida muda em atividades e fragmentos. Neste codelab, você vai explorar o ciclo de vida da atividade em mais detalhes. Você também aprenderá sobre a biblioteca Lifecycle do Android Jetpack, que pode ajudar a gerenciar eventos de ciclo de vida com códigos mais organizados e fáceis 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ê aprenderá

  • Como configurar, iniciar e interromper partes do app nos callbacks do ciclo de vida.
  • Como usar a biblioteca Android lifecycle para criar um observador do 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 e como salvar e restaurar esses dados automaticamente quando o Android é fechado.
  • 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 interrompa esse timer várias vezes no ciclo de vida da atividade.
  • Modifique o app para usar a biblioteca Lifecycle do Android e converta a classe DessertTimer em um observador do ciclo de vida.
  • Configure e use o Android Debug Bridge (adb) para simular o encerramento do processo do app e os callbacks de ciclo de vida que ocorrem em seguida.
  • Implemente o método onSaveInstanceState() para reter os dados que podem ser perdidos se o app for fechado inesperadamente. Como adicionar um código para restaurar esses dados quando o app for iniciado novamente.

Neste codelab, você expandirá o app DessertClicker do codelab anterior. Adicione um timer em segundo plano e converta o app para usar a biblioteca Android lifecycle.

No codelab anterior, você aprendeu a observar os ciclos de vida de atividades e fragmentos substituindo vários callbacks do ciclo de vida e registrando quando o sistema invoca esses callbacks. Nesta tarefa, você verá um exemplo mais complexo de gerenciamento de tarefas do ciclo de vida no app DessertClicker. Você usa um timer que exibe um log statement a cada segundo, com a contagem de segundos de execução.

Etapa 1: configurar o DessertTimer

  1. Abra o app DessertClicker do último codelab. Você pode fazer o download do DessertClickerLogs aqui se não tiver o app.
  2. Na visualização Project, expanda java > com.example.android.dessertclicker e abra DessertTimer.kt. Observe que, no momento, todo o código é comentado e, por isso, 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+/ no Mac). Esse comando remove todo o comentário do código no arquivo. O Android Studio pode exibir erros de referência não resolvidos até você recriar o aplicativo.
  4. A classe DessertTimer inclui startTimer() e stopTimer(), que iniciam e interrompem o timer. Quando o startTimer() está em execução, o timer imprime uma mensagem de registro a cada segundo, com a contagem total de segundos em que o tempo foi executado. O método stopTimer(), por sua vez, interrompe o timer e os log statements.
  1. Abra o MainActivity.kt Na parte superior da classe, logo abaixo da variável dessertsSold, adicione uma variável para o timer:
private lateinit var dessertTimer : DessertTimer;
  1. Role para baixo até encontrar 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 por onde começar e interromper o timer para que ele seja executado somente quando a atividade estiver na tela. Veja algumas opções nas próximas etapas.

Etapa 2: iniciar e parar o timer

O método onStart() é chamado pouco antes de a atividade se tornar visível. O método onStop() é chamado depois que a atividade deixa de ser visível. Esses callbacks parecem bons candidatos para quando 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 filtrará pelas classes MainActivity e DessertTimer. Quando o app for iniciado, o temporizador também será iniciado imediatamente.
  2. Clique no botão Voltar e observe que o timer é interrompido novamente. O timer é interrompido porque a atividade e o timer que ele controla foram destruídos.
  3. Use a tela Recentes para retornar ao app. No Logcat, o timer é reiniciado em 0.
  4. Clique no botão Compartilhar. No Logcat, o timer ainda está em execução.

  5. Clique no botão Home. No Logcat, o timer para de funcionar.
  6. Use a tela Recentes para retornar ao app. No Logcat, o timer inicia novamente de onde parou.
  7. No método onStop() do MainActivity, comente a chamada para stopTimer(). Ao comentar stopTimer(), demonstramos 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 disso, clique no botão home. Embora o app esteja em segundo plano, o temporizador está em execução e continua usando os recursos do sistema. Fazer o timer continuar sendo executado é um vazamento de memória para seu app, e provavelmente não é o comportamento desejado.

    O padrão geral é que, ao configurar ou iniciar algo em um callback, você para ou remove esse gatilho no callback correspondente. Dessa forma, você evita ter que executar nada quando ele não for mais necessário.
  1. Remova o comentário da linha da onStop(), em que você interrompe 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 inicializá-lo e onStart() para iniciá-lo.
  3. Compile e execute o app. Observe que o timer começa a ser executado como esperado.
  4. Clique em "Início" para interromper o app. O timer para de funcionar, como esperado.
  5. Use a tela Recentes para retornar ao app. Nesse caso, o timer não será reiniciado, porque onCreate() será chamado apenas quando o app for iniciado. Ele não é chamado quando um app volta para o primeiro plano.

Pontos importantes:

  • Ao configurar um recurso em um callback do ciclo de vida, também desative o recurso.
  • configurar e desmontar nos métodos correspondentes.
  • Se você configurar algo em onStart(), pare ou desmonte-o novamente em onStop().

No app DessertClicker, é bem fácil ver que, se você iniciou o timer em onStart(), precisa interrompê-lo no onStop(). Como há apenas um timer, parar é difícil de lembrar.

Em um app Android mais complexo, é possível configurar muitas coisas em onStart() ou onCreate() e, em seguida, desmontar tudo em onStop() ou onDestroy(). Por exemplo, pode ser necessário configurar, desmontar e iniciar animações e sensores de música, sensores ou timers. Se você esquecer um deles, isso pode causar bugs e dores de cabeça.

A biblioteca Lifecycle, que faz parte do Android Jetpack, simplifica essa tarefa. A biblioteca é útil principalmente nos casos em que você precisa acompanhar muitas partes móveis, algumas delas em diferentes estados do ciclo de vida. A biblioteca gira a forma como os ciclos de vida funcionam: geralmente, a atividade ou o fragmento informa a um componente (como DessertTimer) o que fazer quando um callback de ciclo de vida ocorre. No entanto, quando você usa a biblioteca de ciclo de vida, o componente em si observa mudanças no ciclo de vida e, em seguida, faz o que é necessário quando essas mudanças acontecem.

Há três partes principais da biblioteca do ciclo de vida:

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

Nesta tarefa, você converterá o app DessertClicker para usar a biblioteca Android lifecycle e aprenderá como a biblioteca facilita o trabalho com a atividade do Android e os ciclos de vida de fragmentos.

Etapa 1: transformar o DessertTimer em um LifecycleObserver

Uma parte essencial da biblioteca de 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 de atividades ou fragmentos, além de iniciar e parar por conta própria em resposta a mudanças nesses estados do ciclo. Com um observador do ciclo de vida, você pode eliminar a responsabilidade de iniciar e parar objetos dos métodos de atividade e fragmento.

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

Essa nova definição de classe tem duas funções:

  • O construtor usa um objeto Lifecycle, que é o ciclo de vida que o temporizador está observando.
  • A definição da 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 do ciclo de vida transmitido do proprietário (a atividade) a essa classe (o observador).
 init {
   lifecycle.addObserver(this)
}
  1. Adicione a anotação @OnLifecycleEvent annotation à startTimer() e use o evento de ciclo de vida ON_START. Todos os eventos do ciclo de vida que o observador do ciclo de vida pode observar estão na classe Lifecycle.Event.
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun startTimer() {
  1. Faça o mesmo para stopTimer() usando o evento ON_STOP:
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stopTimer()

Etapa 2: modificar a MainActivity

A classe MainActivity já é uma proprietária de ciclo de vida pela herança, porque a superclasse FragmentActivity implementa LifecycleOwner. Portanto, você não precisa fazer nada para tornar sua atividade consciente do ciclo de vida. Tudo o que você precisa fazer é transmitir o objeto do ciclo de vida da atividade para o construtor do DessertTimer.

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

A propriedade lifecycle da atividade detém o objeto Lifecycle que essa atividade possui.

  1. Remova a chamada para startTimer() no onCreate() e a chamada para stopTimer() no onStop(). Você não precisa mais dizer ao DessertTimer o que fazer na atividade, porque o DessertTimer agora está observando o ciclo de vida e será notificado automaticamente quando o estado do ciclo de vida mudar. Agora você só precisa registrar uma mensagem nesses callbacks.
  2. Compile e execute o app. Depois, 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 esse app enquanto estiver em segundo plano? É importante entender esse caso complicado.

Quando o app fica em segundo plano, ele não é destruído, só para e aguarda o usuário retornar. No entanto, uma das principais preocupações do SO Android é manter a atividade em primeiro plano sem problemas. Por exemplo, se o usuário estiver usando um app de GPS para ajudá-lo a pegar um ônibus, será importante renderizar esse app rapidamente e continuar mostrando as rotas. É menos importante manter o app DessertClicker, que o usuário pode não ter visto por alguns dias, sendo executado sem problemas em segundo plano.

O Android regula os apps em segundo plano para que o app 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 até encerra um processo inteiro do app, o que inclui 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 navegar de volta para um app que o SO Android encerrou, o Android reiniciará esse app.

Nesta tarefa, você simulará um encerramento de processo do Android e examinará o que acontece com o app quando ele for reiniciado.

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 seu computador. Nesta etapa, você usa o adb para fechar o processo do seu app e ver o que acontece quando ele é encerrado pelo Android.

  1. Compile e execute seu app. Clique no cupcake algumas vezes.
  2. Pressione o botão home para colocar o app em segundo plano. Seu app será interrompido, e estará sujeito a 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 Enter.

    Se você vir um grande volume de saída que começa com Android Debug Bridge version X.XX.X e termina com tags to be used by logcat (see logcat —help, não tem problema. Se, em vez disso, você vir adb: command not found, verifique se o comando adb está disponível no seu caminho de execução. Para ver 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 Enter:
adb shell am kill com.example.android.dessertclicker

Esse comando instrui os dispositivos ou emuladores conectados a interromper o processo com o nome do pacote dessertclicker, mas somente se o app estiver em segundo plano. Como seu app estava em segundo plano, nada é exibido na tela do dispositivo ou emulador para indicar que o processo foi interrompido. No Android Studio, clique na guia Run para ver uma mensagem que diz "quoquo;Aplicativo encerrado"." Clique na guia Logcat para ver se o callback onDestroy() nunca foi executado. Sua atividade simplesmente terminou.

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

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

    No entanto, às vezes o SO Android não sabe sobre 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 ou 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 do 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 de atualização do 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. 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 manter os dados nele pequenos. O tamanho do pacote também é limitado, mas varia de acordo com o dispositivo. Geralmente, você precisa armazenar menos de 100 mil. Caso contrário, há o risco de o app falhar devido ao erro TransactionTooLargeException.
  2. Em onSaveInstanceState(), coloque o valor revenue (um número inteiro) no pacote com 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 o 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, este pacote no 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 à onCreate(), após a configuração do 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 "Início" 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 retornar ao app. Desta vez, o app retornará com a receita correta e os valores vendidos de sobremesas do pacote. Mas observe que a imagem da sobremesa voltou a ser um cupcake. Falta fazer mais uma coisa para garantir que o app retorne do modo de desligamento exatamente como estava.
  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. Em 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 e coloque-o em segundo plano. Use adb para encerrar o processo. Use a tela Recentes para retornar ao app. Os valores das sobremesas, a 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 dos seus 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 mudar o idioma do dispositivo, todo o layout precisará ser alterado para acomodar diferentes direções de texto. Se o usuário conectar o dispositivo a uma base ou adicionar um teclado físico, o layout do app poderá 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: explorar 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. No MainActivity, comente todo o método onSaveInstanceState().
  5. Compile e execute seu app novamente. Clique no cupcake algumas vezes e gire o dispositivo ou emulador. Dessa vez, quando o dispositivo for girado e a atividade for encerrada e recriada, ela será iniciada com valores padrão.

    Quando ocorre uma mudança de configuração, 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. Como no encerramento de um processo, use onSaveInstanceState() para colocar os dados do app no pacote. Em seguida, restaure os dados no onCreate() para evitar a perda de dados do estado da atividade caso a rotação do dispositivo seja feita.
  6. No MainActivity, remova a marca de comentário do método onSaveInstanceState(), execute o app, clique no cupcake e gire o app ou dispositivo. Desta vez, os dados de sobremesas são retidos na rotação da atividade.

Projeto do Android Studio: DessertClickerFinal

Dicas do ciclo de vida

  • Se você configurar ou iniciar um retorno de chamada do ciclo de vida, pare ou remova esse elemento do callback correspondente. Ao interromper o processo, você garante que ele não continuará em execução quando não for mais necessário. Por exemplo, se você configurar um timer em onStart(), será necessário pausar ou parar o timer em onStop().
  • Use onCreate() somente para inicializar as partes do app que são executadas uma vez, quando ele é iniciado. Use onStart() para iniciar as partes do app que são executadas quando ele é iniciada e sempre que ele retorna ao primeiro plano.

Biblioteca Lifecycle

  • Use a biblioteca Android lifecycle para mudar o controle do ciclo de vida da atividade ou do fragmento para o componente real que precisa ser compatível com o ciclo de vida.
  • Os proprietários do ciclo de vida são componentes que têm (e, portanto, "próprios) ciclos de vida, incluindo Activity e Fragment. Os proprietários do 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 o ciclo de vida muda. Observadores do 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 é modificado.

Para criar uma classe com reconhecimento de ciclo de vida:

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

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

Processar encerramentos e salvar o estado da atividade

  • O Android regula os apps em execução em segundo plano para que o app em primeiro plano seja executado sem problemas. Essa regulamentação inclui a limitação da quantidade de processamento que os apps em segundo plano podem fazer e, às vezes, o encerramento de todo o processo de apps.
  • O usuário não consegue saber se o sistema encerrou um app em segundo plano. O app ainda aparecerá na tela Recentes e será reiniciado no mesmo estado em que o usuário o fechou.
  • O Android Debug Bridge (adb) é uma ferramenta de linha de comando que permite enviar instruções para emuladores e dispositivos conectados ao seu computador. Você pode usar o adb para simular um encerramento de processo no seu app.
  • Quando o Android encerra o processo do app, o método de ciclo de vida onDestroy() não é chamado. O app será interrompido.

Como preservar a atividade e o estado do fragmento

  • 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 é desligar 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 para os alunos que estão trabalhando neste codelab como parte de um curso ministrado por um instrutor. Cabe ao instrutor fazer o seguinte:

  • Se necessário, atribua o dever de casa.
  • Informe aos alunos como enviar o dever de casa.
  • Atribua nota aos trabalhos de casa.

Os professores podem usar essas sugestões o quanto quiserem, e eles devem se sentir à vontade para passar o dever de casa como achar adequado.

Se você estiver fazendo este codelab por conta própria, use essas atividades para testar seu conhecimento.

Mudar um app

Abra o app DiceRoller da Lição 1. Se você não tiver o app instalado, faça o download dele aqui. Compile e execute o app. Observe que, se você girar o dispositivo, o valor atual do dado será perdido. Implemente onSaveInstanceState() para manter esse valor no pacote e restaure esse valor em onCreate().

Responda a estas perguntas

Pergunta 1

Seu app contém uma simulação de física que requer computação avançada para ser exibida. Em seguida, o usuário recebe uma chamada telefônica. Qual destas afirmativas é verdadeira?

  • Durante a chamada telefônica, continue calculando as posições dos objetos na simulação física.
  • Durante a chamada telefônica, 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 consciente do ciclo de vida com a biblioteca de ciclo de vida do Android, qual interface a classe precisa implementar?

  • Lifecycle
  • LifecycleOwner
  • Lifecycle.Event
  • LifecycleObserver

Pergunta 4

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

  • A atividade é reiniciada após a rotação do dispositivo.
  • A atividade será iniciada do zero.
  • A atividade é retomada depois de ser retornada em segundo plano.
  • Se o dispositivo for reinicializado.

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

Para ver links de outros codelabs neste curso, consulte a página de destino dos codelabs do curso Conceitos básicos do Kotlin para Android.