Ciclo de vida Android: atividades, fragments e observação segura de estado

Capítulo 7

Tempo estimado de leitura: 9 minutos

+ Exercício

O que é “ciclo de vida” e por que ele muda seu código

No Android, telas e componentes visuais não ficam “vivos” o tempo todo. O sistema cria, mostra, pausa, para e destrói Activity e Fragment conforme o usuário navega, gira a tela, alterna apps, recebe uma ligação, entra em modo multi-janela etc. O ciclo de vida é o conjunto de estados e callbacks que avisam seu código sobre essas transições.

Isso afeta diretamente:

  • UI: você só pode acessar views quando elas existem (especialmente em Fragment).
  • Requisições e assinaturas: observar dados, coletar Flow, registrar listeners (sensores, localização, callbacks) deve respeitar o estado atual para evitar trabalho desnecessário e vazamentos.
  • Estabilidade: muitos crashes acontecem por tentar atualizar UI quando a tela já foi destruída, ou por manter referência a uma view antiga.

Callbacks de Activity: o que acontece em cada fase

Principais callbacks (ordem típica ao abrir e fechar):

CallbackQuando ocorreUso típico
onCreateCriação inicial da ActivityInflar layout, inicializar dependências, configurar observers (com cuidado), restaurar estado
onStartActivity vai ficar visívelPreparar recursos que precisam existir enquanto visível
onResumeActivity em primeiro plano e interativaIniciar animações, câmera, sensores, listeners de UI “ativos”
onPausePerde foco (pode ainda estar parcialmente visível)Pausar operações sensíveis, persistir pequenas mudanças, liberar recursos exclusivos
onStopNão está mais visívelParar atualizações contínuas, cancelar assinaturas pesadas
onDestroyFinalização (nem sempre garantido em casos extremos)Limpeza final; evite depender disso para salvar dados

Ponto prático: não é porque algo foi iniciado em onCreate que ele deve ficar rodando até onDestroy. Muitas operações devem acompanhar STARTED ou RESUMED para economizar bateria e evitar atualizar UI fora de hora.

Callbacks de Fragment: duas “vidas” diferentes (Fragment vs View)

Em Fragment, existe o ciclo de vida do Fragment e o ciclo de vida da View do Fragment. A view pode ser destruída e recriada (por exemplo, ao navegar e voltar, ou ao usar ViewPager/Navigation), enquanto o Fragment pode continuar existindo.

Continue em nosso aplicativo e ...
  • Ouça o áudio com a tela desligada
  • Ganhe Certificado após a conclusão
  • + de 5000 cursos para você explorar!
ou continue lendo abaixo...
Download App

Baixar o aplicativo

Callbacks mais relevantes para evitar crashes:

CallbackO que significaUso típico
onCreateFragment criado (sem view ainda)Inicializar coisas que não dependem de view
onCreateViewCria/infla a viewInflar layout e criar binding
onViewCreatedView prontaConfigurar UI, listeners, adapters, observers ligados à view
onStart/onResumeFragment visível/interativoIniciar coleta/atualizações que dependem de visibilidade
onPause/onStopPerde foco/não visívelPausar atualizações
onDestroyViewA view será destruídaLimpar referências a view/binding
onDestroyFragment destruídoLimpeza final do Fragment

Regra de ouro: tudo que referencia view (binding, adapters com callback para view, listeners em views) deve ser criado após onCreateView e liberado em onDestroyView.

Prática 1: visualizar transições no Logcat (Activity e Fragment)

Passo a passo: Activity com logs

1) Crie um tag e logue cada callback. Use o Logcat para ver a ordem real conforme você abre a tela, aperta Home, volta, gira o aparelho.

class MainActivity : AppCompatActivity() {    private val TAG = "Lifecycle-MainActivity"    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        Log.d(TAG, "onCreate")        setContentView(R.layout.activity_main)    }    override fun onStart() {        super.onStart()        Log.d(TAG, "onStart")    }    override fun onResume() {        super.onResume()        Log.d(TAG, "onResume")    }    override fun onPause() {        Log.d(TAG, "onPause")        super.onPause()    }    override fun onStop() {        Log.d(TAG, "onStop")        super.onStop()    }    override fun onDestroy() {        Log.d(TAG, "onDestroy")        super.onDestroy()    }}

2) No Logcat, filtre por Lifecycle-MainActivity.

3) Experimentos rápidos:

  • Abrir a tela: observe onCreate → onStart → onResume.
  • Apertar Home: observe onPause → onStop.
  • Voltar ao app: observe onStart → onResume.
  • Fechar a Activity (Back): observe onPause → onStop → onDestroy.

Passo a passo: Fragment com logs (incluindo onDestroyView)

class ExampleFragment : Fragment(R.layout.fragment_example) {    private val TAG = "Lifecycle-ExampleFragment"    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        Log.d(TAG, "onCreate")    }    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {        super.onViewCreated(view, savedInstanceState)        Log.d(TAG, "onViewCreated")    }    override fun onStart() {        super.onStart()        Log.d(TAG, "onStart")    }    override fun onResume() {        super.onResume()        Log.d(TAG, "onResume")    }    override fun onPause() {        Log.d(TAG, "onPause")        super.onPause()    }    override fun onStop() {        Log.d(TAG, "onStop")        super.onStop()    }    override fun onDestroyView() {        Log.d(TAG, "onDestroyView")        super.onDestroyView()    }    override fun onDestroy() {        Log.d(TAG, "onDestroy")        super.onDestroy()    }}

Teste navegar para outro Fragment e voltar. Você verá frequentemente onDestroyView acontecer mesmo quando onDestroy não acontece imediatamente. Isso explica muitos bugs de “tentei acessar view e crashou”.

LifecycleOwner: quem “possui” um ciclo de vida

LifecycleOwner é uma interface que representa algo que tem ciclo de vida (por exemplo, ComponentActivity e Fragment). Ela permite que componentes observem mudanças de estado sem você precisar manualmente “desligar” tudo em cada callback.

No dia a dia, os dois LifecycleOwner mais comuns em um Fragment são:

  • this (o ciclo de vida do Fragment)
  • viewLifecycleOwner (o ciclo de vida da view do Fragment)

Para qualquer coisa que atualize UI (TextView, RecyclerView, visibilidade, etc.), prefira viewLifecycleOwner. Assim, quando a view for destruída, a observação para automaticamente, evitando atualizar uma view inexistente.

repeatOnLifecycle: coletar estado com segurança (e parar automaticamente)

repeatOnLifecycle é a forma recomendada de coletar Flow (e outras operações suspensas) respeitando o ciclo de vida. Ele inicia o bloco quando o Lifecycle atinge um estado mínimo (ex.: STARTED) e cancela quando cai abaixo desse estado, reiniciando quando voltar.

Exemplo prático: coletar UI state em Activity

class MainActivity : AppCompatActivity() {    private val viewModel: MainViewModel by viewModels()    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_main)        lifecycleScope.launch {            repeatOnLifecycle(Lifecycle.State.STARTED) {                viewModel.uiState.collect { state ->                    // Atualize UI com segurança enquanto STARTED+                    findViewById<TextView>(R.id.statusText).text = state.message                }            }        }    }

Por que STARTED? Porque a tela está visível. Se você usar RESUMED, a coleta só acontece quando a Activity está em primeiro plano e interativa; pode ser útil para coisas que não devem rodar em segundo plano (ex.: câmera).

Exemplo prático: coletar UI state em Fragment (correto)

class ExampleFragment : Fragment(R.layout.fragment_example) {    private val viewModel: ExampleViewModel by viewModels()    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {        super.onViewCreated(view, savedInstanceState)        val statusText = view.findViewById<TextView>(R.id.statusText)        viewLifecycleOwner.lifecycleScope.launch {            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {                viewModel.uiState.collect { state ->                    statusText.text = state.message                }            }        }    }}

Detalhe importante: note o uso de viewLifecycleOwner em dois lugares: no lifecycleScope e no repeatOnLifecycle. Isso garante que a coroutine e a coleta sejam canceladas quando a view morrer.

Anti-exemplo comum (evite)

// Evite: usa lifecycle do Fragment, não da view. Pode tentar atualizar UI após onDestroyView. lifecycleScope.launch {    repeatOnLifecycle(Lifecycle.State.STARTED) {        viewModel.uiState.collect { state ->            binding.statusText.text = state.message        }    }}

Se a view for destruída e o Fragment continuar, binding pode estar nulo/invalidado e você terá crash ou vazamento.

Como evitar vazamentos de memória e referências a views destruídas

1) ViewBinding em Fragment: limpar no onDestroyView

Quando usar ViewBinding em Fragment, mantenha uma referência anulável e limpe no momento certo.

class ExampleFragment : Fragment(R.layout.fragment_example) {    private var _binding: FragmentExampleBinding? = null    private val binding get() = _binding!!    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {        super.onViewCreated(view, savedInstanceState)        _binding = FragmentExampleBinding.bind(view)        binding.button.setOnClickListener {            // ...        }    }    override fun onDestroyView() {        super.onDestroyView()        _binding = null    }}

Isso evita manter a árvore de views na memória após a navegação.

2) Listeners e callbacks: registrar e remover conforme o lifecycle

Se você registra algo manualmente (ex.: listener em um serviço, callback de localização, observer customizado), remova quando a tela não estiver mais ativa. Se o listener captura a Activity/Fragment, ele pode impedir o GC (vazamento).

  • Registre em onStart ou onResume
  • Remova em onStop ou onPause

3) Coroutines: prefira escopos corretos

  • Em Activity: lifecycleScope é adequado para tarefas ligadas à tela.
  • Em Fragment: para UI, prefira viewLifecycleOwner.lifecycleScope.
  • Evite GlobalScope para tarefas de UI; ele ignora o ciclo de vida e tende a causar vazamentos e atualizações fora de hora.

4) Não guarde View/Context em singletons

Guardar Activity, View ou Context de tela em objetos globais/singletons é uma fonte clássica de vazamento. Se precisar de contexto de aplicação, use applicationContext (quando apropriado) e evite referências a UI.

Prática 2: pausar/retomar uma operação conforme o lifecycle

Objetivo: simular uma operação contínua (um “ticker” que emite números) que deve rodar apenas quando a tela estiver visível. Vamos fazer isso com Flow e repeatOnLifecycle.

Passo 1: criar um Flow que emite valores periodicamente

class TickerRepository {    val tickerFlow: kotlinx.coroutines.flow.Flow<Int> = kotlinx.coroutines.flow.flow {        var i = 0        while (true) {            emit(i++)            kotlinx.coroutines.delay(1000)        }    }}

Passo 2: expor no ViewModel (opcional, mas comum)

class ExampleViewModel : ViewModel() {    private val repo = TickerRepository()    val ticker = repo.tickerFlow }

Passo 3: coletar na UI com repeatOnLifecycle (pausa/retoma automaticamente)

Em Activity:

class MainActivity : AppCompatActivity() {    private val viewModel: ExampleViewModel by viewModels()    private val TAG = "Ticker"    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_main)        val text = findViewById<TextView>(R.id.counterText)        lifecycleScope.launch {            repeatOnLifecycle(Lifecycle.State.STARTED) {                Log.d(TAG, "Coleta INICIADA (STARTED)")                viewModel.ticker.collect { value ->                    text.text = "Contador: $value"                    Log.d(TAG, "Emitido: $value")                }            }            // Quando sair de STARTED, o bloco acima é cancelado e reiniciado ao voltar        }    }}

Teste no dispositivo/emulador:

  • Abra a tela: o contador começa.
  • Aperte Home: a coleta para (veja no Logcat que parou de emitir).
  • Volte ao app: a coleta reinicia automaticamente.

Se você quiser que a operação rode apenas quando a tela estiver realmente em primeiro plano (interativa), troque Lifecycle.State.STARTED por Lifecycle.State.RESUMED.

Checklist rápido: onde colocar cada tipo de código

Tipo de códigoActivityFragment
Inflar layout / bindingonCreateonCreateView/onViewCreated
Configurar listeners de UIonCreate (após setContentView)onViewCreated
Coletar Flow para atualizar UIlifecycleScope + repeatOnLifecycleviewLifecycleOwner.lifecycleScope + repeatOnLifecycle
Registrar/desregistrar recursos (sensores, localização, câmera)onResume/onPause ou onStart/onStoponResume/onPause ou onStart/onStop
Limpar referência de view/bindingNão se aplica (view vive com a Activity)onDestroyView

Agora responda o exercício sobre o conteúdo:

Em um Fragment, qual abordagem é mais segura para coletar um Flow que atualiza a UI e evitar crashes após a destruição da view?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

Em Fragments, a view pode ser destruída antes do Fragment. Ao usar viewLifecycleOwner no lifecycleScope e no repeatOnLifecycle, a coleta é cancelada quando a view morre, evitando atualizar UI inexistente e reduzindo vazamentos.

Próximo capitúlo

Arquitetura Android com Kotlin: camadas, MVVM e fluxo de dados

Arrow Right Icon
Capa do Ebook gratuito Android para Iniciantes com Kotlin: construindo seu primeiro app moderno
47%

Android para Iniciantes com Kotlin: construindo seu primeiro app moderno

Novo curso

15 páginas

Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.