Conceitos básicos do Android 02.2: estado e ciclo de vida da atividade

1. Olá!

Introdução

Nesta seção, você vai saber mais sobre o ciclo de vida da atividade. O ciclo de vida é o conjunto de estados em que uma atividade pode estar durante toda a vida útil, desde quando é criada até ser destruída e o sistema recuperar os recursos. À medida que o usuário navega entre as atividades no app (e dentro e fora dele), as atividades fazem a transição entre estados diferentes no ciclo de vida.

Diagrama do ciclo de vida do app

Cada estágio do ciclo de vida de uma atividade tem um método de callback correspondente: onCreate(), onStart(), onPause() e assim por diante. Quando uma atividade muda de estado, o método de callback associado é invocado. Você já conhece um desses métodos: onCreate(). Ao substituir qualquer um dos métodos de callback do ciclo de vida nas classes Activity, é possível mudar o comportamento padrão da atividade em resposta a ações do usuário ou do sistema.

O estado da atividade também pode mudar em resposta a mudanças na configuração do dispositivo, por exemplo, quando o usuário gira o dispositivo de retrato para paisagem. Quando essas mudanças de configuração ocorrem, a atividade é destruída e recriada no estado padrão, e o usuário pode perder as informações inseridas na atividade. Para evitar confundir os usuários, é importante desenvolver o app para evitar a perda inesperada de dados. Mais adiante neste curso, você vai testar mudanças de configuração e aprender a preservar o estado de uma atividade em resposta a mudanças de configuração do dispositivo e outros eventos do ciclo de vida da atividade.

Você vai adicionar log statements ao app TwoActivities e observar as mudanças no ciclo de vida da atividade durante o uso do app. Depois, você vai trabalhar com essas mudanças e aprender como lidar com as entradas do usuário nessas condições.

O que você já precisa saber

Você precisa saber:

  • Criar e executar um projeto de app no Android Studio.
  • Adicionar log statements ao app e observar esses logs no painel Logcat.
  • Entender e trabalhar com uma Activity e uma Intent e saber interagir com elas.

O que você vai aprender

  • Como o ciclo de vida da Activity funciona.
  • Quando uma Activity é iniciada, pausada, interrompida e destruída.
  • Sobre os métodos de callback do ciclo de vida associados às mudanças da Activity.
  • O efeito de ações (como mudanças de configuração) que podem resultar em eventos de ciclo de vida da Activity.
  • Como preservar o estado da Activity em eventos de ciclo de vida.

O que você vai fazer

  • Adicionar código ao app TwoActivities do exercício prático anterior para implementar os vários callbacks de ciclo de vida de Activity e incluir log statements.
  • Observar as mudanças de estado à medida que o app é executado e você interage com cada Activity no app.
  • Modificar o app para preservar o estado da instância de uma Activity que é recriada inesperadamente em resposta ao comportamento do usuário ou a mudanças de configuração no dispositivo.

2. Visão geral do app

Neste exercício, você vai adicionar código ao app TwoActivities (link em inglês). O app vai ter a mesma aparência e o mesmo comportamento do último codelab. Ele contém duas implementações de Activity e permite que o usuário navegue entre elas. As mudanças que você fizer no app neste exercício não afetarão o comportamento visível ao usuário.

3. Tarefa 1: adicionar callbacks do ciclo de vida ao app TwoActivities

Nesta tarefa, você vai implementar todos os métodos de callback do ciclo de vida da Activity para mostrar mensagens no logcat quando esses métodos forem invocados. Essas mensagens de registro permitem que você saiba quando o ciclo de vida da Activity muda de estado e como essas mudanças afetam o app durante a execução.

1.1 (Opcional) Copiar o projeto TwoActivities

Para as tarefas deste curso, você vai modificar o projeto TwoActivities existente criado na última parte prática. Se você preferir manter o projeto TwoActivities anterior intacto, siga as etapas em Apêndice: utilitários para fazer uma cópia do projeto (links em inglês).

1.2 Implementar callbacks na MainActivity

  1. Abra o projeto TwoActivities no Android Studio e abra MainActivity no painel Project > Android.
  2. No método onCreate(), adicione estes log statements:
Log.d(LOG_TAG, "-------");
Log.d(LOG_TAG, "onCreate");
  1. Adicione uma substituição para o callback onStart(), com uma instrução no registro desse evento:
@Override
public void onStart(){
    super.onStart();
    Log.d(LOG_TAG, "onStart");
}

Para um atalho, selecione Code > Override Methods no Android Studio. Uma caixa de diálogo aparecerá com todos os métodos possíveis que você pode substituir na classe. A escolha de um ou mais métodos de callback da lista insere um modelo completo para esses métodos, incluindo a chamada necessária para a superclasse.

  1. Use o método onStart() como modelo para implementar os callbacks de ciclo de vida onPause(), onRestart(), onResume(), onStop() e onDestroy().

Todos os métodos de callback têm as mesmas assinaturas (exceto o nome). Se você Copiar e Colar onStart() para criar esses outros métodos de callback, não se esqueça de atualizar o conteúdo para chamar o método certo na superclasse e registrar o método correto.

  1. Execute o app.
  2. Clique na guia Logcat na parte de baixo do Android Studio para mostrar o painel Logcat. Vão aparecer três mensagens de registro mostrando os três estados do ciclo de vida pelos quais a Activity passou:
D/MainActivity: -------
D/MainActivity: onCreate
D/MainActivity: onStart
D/MainActivity: onResume

1.3 Implementar callbacks do ciclo de vida na SecondActivity

Agora que você implementou os métodos de callback do ciclo de vida para MainActivity, faça o mesmo para SecondActivity.

  1. Abra SecondActivity.
  2. Na parte de cima da classe, adicione uma constante para a variável LOG_TAG:
private static final String LOG_TAG = SecondActivity.class.getSimpleName();
  1. Adicione os callbacks do ciclo de vida e os log statements à segunda Activity. Você pode Copiar e Colar os métodos de callback da MainActivity.
  2. Adicione um log statement ao método returnReply() antes do método finish():
Log.d(LOG_TAG, "End SecondActivity");

1.4 Observar os registros enquanto o app é executado

  1. Execute o app.
  2. Clique na guia Logcat na parte de baixo do Android Studio para mostrar o painel Logcat.
  3. Digite Activity na caixa de pesquisa. O logcat do Android pode ser muito longo e confuso. Como a variável LOG_TAG em cada classe contém as palavras MainActivity ou SecondActivity, essa palavra-chave permite filtrar o registro apenas para as coisas em que você se interessa.

Registro mostrando estados do ciclo de vida

Tente usar seu app e observe os eventos do ciclo de vida que ocorrem em resposta a diferentes ações. Em especial, tente realizar estas ações:

  • Use o app normalmente (envie uma mensagem, responda com outra).
  • Use o botão "Voltar" para voltar da segunda Activity para a Activity principal.
  • Use a seta para cima na barra de apps para voltar da segunda Activity para a Activity principal.
  • Gire o dispositivo tanto na primeira quanto na segunda Activity em diferentes momentos no seu app e observe o que acontece no registro e na tela.
  • Pressione o botão de visão geral (o botão quadrado à direita do botão home) e feche o app (toque no X).
  • Volte para a tela inicial e reinicie o app.

DICA: se você estiver executando o app em um emulador, poderá simular a rotação com Control+F11 ou Control+Function+F11.

Código da solução da tarefa 1

Os snippets de código abaixo mostram o código da solução para a primeira tarefa.

MainActivity

Os snippets de código abaixo mostram o código adicionado em MainActivity, mas não a classe inteira.

O método onCreate():

@Override
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Log the start of the onCreate() method.
        Log.d(LOG_TAG, "-------");
        Log.d(LOG_TAG, "onCreate");

        // Initialize all the view variables.
        mMessageEditText = findViewById(R.id.editText_main);
        mReplyHeadTextView = findViewById(R.id.text_header_reply);
        mReplyTextView = findViewById(R.id.text_message_reply);
}

Os outros métodos de ciclo de vida:

@Override
protected void onStart() {
        super.onStart();
        Log.d(LOG_TAG, "onStart");
}

@Override
protected void onPause() {
        super.onPause();
        Log.d(LOG_TAG, "onPause");
}

@Override
protected void onRestart() {
        super.onRestart();
        Log.d(LOG_TAG, "onRestart");
}

@Override
protected void onResume() {
        super.onResume();
        Log.d(LOG_TAG, "onResume");
}

@Override
protected void onStop() {
        super.onStop();
        Log.d(LOG_TAG, "onStop");
}

@Override
protected void onDestroy() {
        super.onDestroy();
        Log.d(LOG_TAG, "onDestroy");
}

SecondActivity

Os snippets de código abaixo mostram o código adicionado em SecondActivity, mas não a classe inteira.

Na parte de cima da classe SecondActivity:

private static final String LOG_TAG = SecondActivity.class.getSimpleName();

O método returnReply():

public void returnReply(View view) {
        String reply = mReply.getText().toString();
        Intent replyIntent = new Intent();
        replyIntent.putExtra(EXTRA_REPLY, reply);
        setResult(RESULT_OK, replyIntent);
        Log.d(LOG_TAG, "End SecondActivity");
        finish();
}

Os outros métodos de ciclo de vida:

Igual à MainActivity acima.

4. Tarefa 2: salvar e restaurar o estado da instância da atividade

Dependendo dos recursos do sistema e do comportamento do usuário, cada Activity no seu app pode ser destruída e reconstruída com muito mais frequência do que você imagina.

Você pode ter notado esse comportamento na última seção ao girar o dispositivo ou emulador. A rotação do dispositivo é um exemplo de mudança de configuração do dispositivo. Embora a rotação seja a mais comum, todas as mudanças de configuração fazem com que a Activity atual seja destruída e recriada como se fosse nova. Se você não considerar esse comportamento no seu código, quando uma mudança de configuração ocorrer, o layout da Activity poderá ser revertido para a aparência padrão e os valores iniciais, e os usuários poderão perder o lugar, os dados ou o estado do progresso deles no seu app.

O estado de cada Activity é armazenado como um conjunto de pares de chave-valor em um objeto Bundle chamado de estado da instância da Activity. O sistema salva as informações de estado padrão no estado da instânciaBundle antes da Activity ser interrompida e transmite o Bundle à nova instância da Activity que será restaurada.

Para evitar a perda de dados de Activity quando ela é destruída e recriada inesperadamente, você precisa implementar o método onSaveInstanceState(). O sistema chama esse método na Activity (entre onPause() e onStop()) quando há a possibilidade de a Activity ser destruída e recriada.

Os dados salvos no estado da instância são específicos apenas para essa instância dessa Activity específica durante a sessão atual do app. Quando você interrompe e reinicia uma nova sessão do app, o estado da instância da Activity é perdido e a Activity é revertida para a aparência padrão. Se você precisar salvar dados do usuário entre sessões do app, use as preferências compartilhadas ou um banco de dados. Você vai aprender sobre isso no futuro em um exercício prático.

2.1 Salvar o estado da instância de atividade com onSaveInstanceState()

Você pode ter notado que girar o dispositivo não afeta o estado da segunda Activity. Isso ocorre porque o layout e o estado da segunda Activity são gerados a partir do layout e da Intent que o ativou. Mesmo que a Activity seja recriada, a Intent ainda estará presente e os dados nessa Intent ainda serão usados sempre que o método onCreate() na segunda Activity for chamado.

Além disso, você pode notar que, em cada Activity, qualquer texto digitado em elementos EditText de mensagem ou de resposta é mantido, mesmo quando o dispositivo é girado. Isso ocorre porque as informações de estado de alguns elementos View no layout são salvas automaticamente nas mudanças de configuração, e o valor atual de um EditText é um desses casos.

O único estado da Activity em que você tem interesse são os elementos TextView do cabeçalho e do texto da resposta na Activity principal. Os dois elementos TextView são invisíveis por padrão. Eles só aparecem quando você envia uma mensagem de volta para a Activity principal a partir da segunda Activity.

Nesta tarefa, você vai adicionar um código para preservar o estado da instância desses dois elementos TextView usando onSaveInstanceState().

  1. Abra a MainActivity.
  2. Adicione essa implementação básica de onSaveInstanceState() à Activity ou use Code > Override Methods para inserir uma substituição.
@Override
public void onSaveInstanceState(Bundle outState) {
          super.onSaveInstanceState(outState);
}
  1. Verifique se o cabeçalho está visível no momento. Caso esteja, coloque esse estado de visibilidade no estado Bundle com o método putBoolean() e a chave "reply_visible".
    if (mReplyHeadTextView.getVisibility() == View.VISIBLE) {
        outState.putBoolean("reply_visible", true);
    }

Não se esqueça que o cabeçalho e o texto da resposta são marcados como invisíveis até que haja uma resposta da segunda Activity. Se o cabeçalho estiver visível, há dados de resposta que precisam ser salvos. Só estamos interessados nesse estado de visibilidade. O texto real do cabeçalho não precisa ser salvo porque ele nunca muda.

  1. Dentro dessa mesma verificação, adicione o texto de resposta ao Bundle.
outState.putString("reply_text",mReplyTextView.getText().toString());

Se o cabeçalho estiver visível, você poderá presumir que a própria mensagem de resposta também está visível. Não é necessário testar nem salvar o estado de visibilidade atual da mensagem de resposta. Apenas o texto real da mensagem entra no estado Bundle com a chave "reply_text".

Você salva o estado apenas dos elementos View que podem mudar depois que a Activity é criada. Os outros elementos View no seu app (o EditText e o Button) podem ser recriados usando o layout padrão sempre que necessário.

O sistema salvará o estado de alguns elementos View, como o conteúdo de EditText.

2.2 Restaurar o estado da instância da atividade em onCreate()

Depois de salvar o estado da instância da Activity, também é necessário restaurá-lo quando a Activity é recriada. Você pode fazer isso em onCreate() ou implementando o callback onRestoreInstanceState(), que é chamado após onStart() depois da criação da Activity.

Na maioria das vezes, o melhor lugar para restaurar o estado da Activity é em onCreate(), para garantir que a interface, incluindo o estado, esteja disponível o mais rápido possível. Às vezes, é conveniente fazer isso em onRestoreInstanceState() depois que toda a inicialização é concluída ou permitir que subclasses decidam se a implementação padrão será usada.

  1. No método onCreate(), depois que as variáveis View forem inicializadas com findViewById(), adicione um teste para garantir que savedInstanceState não seja nulo.
// Initialize all the view variables.
mMessageEditText = findViewById(R.id.editText_main);
mReplyHeadTextView = findViewById(R.id.text_header_reply);
mReplyTextView = findViewById(R.id.text_message_reply);

// Restore the state.
if (savedInstanceState != null) {
}

Quando a Activity é criada, o sistema transmite o estado Bundle para onCreate() como o único argumento. Na primeira vez que o método onCreate() for chamado e o app for iniciado, o Bundle será null. Não há estado na primeira vez que o app for iniciado. As chamadas subsequentes para onCreate() terão um pacote preenchido com os dados armazenados em onSaveInstanceState().

  1. Dentro dessa verificação, extraia a visibilidade atual (verdadeiro ou falso) do Bundle com a chave "reply_visible".
if (savedInstanceState != null) {
    boolean isVisible =
                     savedInstanceState.getBoolean("reply_visible");
}
  1. Adicione um teste abaixo da linha anterior para a variável isVisible.
if (isVisible) {
}

Se houver uma chave reply_visible no estado Bundle (e isVisible for true), você precisará restaurar o estado.

  1. No teste de isVisible, torne o cabeçalho visível.
mReplyHeadTextView.setVisibility(View.VISIBLE);
  1. Receba a mensagem de resposta de texto de Bundle com a chave "reply_text" e defina a resposta TextView para mostrar essa string.
mReplyTextView.setText(savedInstanceState.getString("reply_text"));
  1. Torne a resposta TextView visível também:
mReplyTextView.setVisibility(View.VISIBLE);
  1. Execute o app. Tente girar o dispositivo ou o emulador para garantir que a mensagem de resposta (se houver) permaneça na tela depois que a Activity for recriada.

Código da solução da tarefa 2

Os snippets de código abaixo mostram o código da solução para essa tarefa.

MainActivity

Os snippets de código abaixo mostram o código adicionado em MainActivity, mas não a classe inteira.

O método onSaveInstanceState():

@Override
public void onSaveInstanceState(Bundle outState) {
   super.onSaveInstanceState(outState);
   // If the heading is visible, message needs to be saved.
   // Otherwise we're still using default layout.
   if (mReplyHeadTextView.getVisibility() == View.VISIBLE) {
       outState.putBoolean("reply_visible", true);
       outState.putString("reply_text", 
                      mReplyTextView.getText().toString());
   }
}

O método onCreate():

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);

   Log.d(LOG_TAG, "-------");
   Log.d(LOG_TAG, "onCreate");

   // Initialize all the view variables.
   mMessageEditText = findViewById(R.id.editText_main);
   mReplyHeadTextView = findViewById(R.id.text_header_reply);
   mReplyTextView = findViewById(R.id.text_message_reply);

   // Restore the saved state. 
   // See onSaveInstanceState() for what gets saved.
   if (savedInstanceState != null) {
       boolean isVisible = 
                     savedInstanceState.getBoolean("reply_visible");
       // Show both the header and the message views. If isVisible is
       // false or missing from the bundle, use the default layout.
       if (isVisible) {
           mReplyHeadTextView.setVisibility(View.VISIBLE);
           mReplyTextView.setText(savedInstanceState
                                  .getString("reply_text"));
           mReplyTextView.setVisibility(View.VISIBLE);
       }
   }
}

O projeto completo:

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

5. Desafio de programação

Desafio: crie um app simples de lista de compras com uma atividade principal para a lista que o usuário está criando e uma segunda atividade para uma lista de itens comuns.

  • A atividade principal precisa conter a lista a ser criada, que precisa ser composta por dez elementos TextView vazios.
  • Um botão Add Item na atividade principal inicia uma segunda atividade que contém uma lista de itens comuns (Queijo, Arroz, Maçãs e assim por diante). Use elementos Button para mostrar os itens.
  • A escolha de um item retorna o usuário à atividade principal e atualiza um elemento TextView vazio para incluir o item escolhido.

Use uma Intent para transmitir informações de uma Activity para outra. O estado atual da lista de compras precisa ser salvo quando o usuário girar o dispositivo.

6. Resumo

  • O ciclo de vida da atividade é um conjunto de estados pelos quais uma Activity passa, começando quando ela é criada pela primeira vez e terminando quando o sistema Android recupera os recursos dessa Activity.
  • À medida que o usuário navega de uma Activity para outra, e dentro e fora do app, cada Activity se move entre estados no ciclo de vida da Activity.
  • Cada estado do ciclo de vida da Activity tem um método de callback correspondente que você pode substituir na classe Activity.
  • Os métodos do ciclo de vida são onCreate(), onStart(), onPause(), onRestart(), onResume(), onStop() e onDestroy().
  • A substituição de um método de callback do ciclo de vida permite adicionar um comportamento que ocorre quando a Activity faz a transição para esse estado.
  • É possível adicionar modelos de métodos de substituição às classes no Android Studio com Code > Override.
  • Mudanças na configuração do dispositivo, como a rotação, fazem com que a Activity seja destruída e recriada como se fosse nova.
  • Uma parte do estado da Activity é preservada em uma mudança de configuração, incluindo os valores atuais dos elementos EditText. Para todos os outros dados, você precisa salvar esses dados explicitamente.
  • Salve o estado da instância da Activity no método onSaveInstanceState().
  • Os dados de estado da instância são armazenados como pares simples de chave-valor em um Bundle. Use os métodos Bundle para armazenar e extrair dados do Bundle.
  • Restaure o estado da instância em onCreate(), que é a maneira recomendada, ou onRestoreInstanceState().

7. Conceito relacionado

A documentação do conceito relacionado está em 2.2: Ciclo de vida e estado da atividade (link em inglês).

8. Saiba mais

Documentação do Android Studio:

Documentação do desenvolvedor Android:

9. Dever de casa

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.

Criar e executar um app

  1. Crie um app com um layout que contenha um contador de TextView, um Button para incrementar o contador e um EditText. Como exemplo, confira a captura de tela abaixo. Não é necessário copiar exatamente o layout.
  2. Adicione um gerenciador de cliques para o Button que incrementa o contador.
  3. Execute o app e incremente o contador. Digite um texto no EditText.
  4. Gire o dispositivo. Observe que o contador é redefinido, mas o EditText não.
  5. Implemente onSaveInstanceState() para salvar o estado atual do app.
  6. Atualize o método onCreate() para restaurar o estado do app.
  7. Ao girar o dispositivo, o estado do app precisa ser preservado.

ebaf84570af6dd46.png

Responda estas perguntas

Pergunta 1

Se você executar o app de dever de casa antes de implementar onSaveInstanceState(), o que acontecerá ao girar o dispositivo? Escolha uma:

  • O EditText não conterá mais o texto que você digitou, mas o contador será preservado.
  • O contador será redefinido como 0, e o EditText não conterá mais o texto que você digitou.
  • O contador será redefinido como 0, mas o conteúdo do EditText será preservado.
  • O contador e o conteúdo do EditText serão preservados.

Pergunta 2

Quais métodos do ciclo de vida de Activity são chamados quando ocorre uma mudança na configuração do dispositivo (como a rotação)? Escolha uma:

  • O Android encerra imediatamente a Activity chamando onStop(). Seu código precisa reiniciar a Activity.
  • O Android encerra a Activity chamando onPause(), onStop() e onDestroy(). Seu código precisa reiniciar a Activity.
  • O Android encerra a Activity chamando onPause(), onStop() e onDestroy() e a reinicia novamente, chamando onCreate(), onStart() e onResume().
  • O Android chama onResume() imediatamente.

Pergunta 3

Quando o método onSaveInstanceState() é chamado no ciclo de vida da Activity? Escolha uma:

  • onSaveInstanceState() é chamado antes do método onStop().
  • onSaveInstanceState() é chamado antes do método onResume().
  • onSaveInstanceState() é chamado antes do método onCreate().
  • onSaveInstanceState() é chamado antes do método onDestroy().

Pergunta 4

Quais métodos de ciclo de vida da Activity são melhores para salvar dados antes que a Activity seja concluída ou destruída? Escolha uma:

  • onPause() ou onStop()
  • onResume() ou onCreate()
  • onDestroy()
  • onStart() ou onRestart()

Enviar o app para avaliação

Orientação para os avaliadores

Verifique se o app tem estes recursos:

  • Ele mostra um contador, um Button para incrementar esse contador e um EditText.
  • Clicar em Button aumenta o contador em 1.
  • Quando o dispositivo é girado, os estados do contador e EditText são preservados.
  • A implementação de MainActivity.java usa o método onSaveInstanceState() para armazenar o valor do contador.
  • A implementação de testes de onCreate() para a existência do Bundle do outState. Se esse Bundle existir, o valor do contador será restaurado e salvo no elemento TextView.

10. Próximo codelab

Para encontrar o próximo codelab prático do curso de noções básicas para desenvolvedores Android (V2), consulte os codelabs para o curso Conceitos básicos para desenvolvedores Android (V2).

Para ter uma visão geral do curso, incluindo links para os capítulos do conceito, apps e slides, consulte Conceitos básicos para desenvolvedores Android (versão 2).