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):
| Callback | Quando ocorre | Uso típico |
|---|---|---|
onCreate | Criação inicial da Activity | Inflar layout, inicializar dependências, configurar observers (com cuidado), restaurar estado |
onStart | Activity vai ficar visível | Preparar recursos que precisam existir enquanto visível |
onResume | Activity em primeiro plano e interativa | Iniciar animações, câmera, sensores, listeners de UI “ativos” |
onPause | Perde foco (pode ainda estar parcialmente visível) | Pausar operações sensíveis, persistir pequenas mudanças, liberar recursos exclusivos |
onStop | Não está mais visível | Parar atualizações contínuas, cancelar assinaturas pesadas |
onDestroy | Finalizaçã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.
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
Callbacks mais relevantes para evitar crashes:
| Callback | O que significa | Uso típico |
|---|---|---|
onCreate | Fragment criado (sem view ainda) | Inicializar coisas que não dependem de view |
onCreateView | Cria/infla a view | Inflar layout e criar binding |
onViewCreated | View pronta | Configurar UI, listeners, adapters, observers ligados à view |
onStart/onResume | Fragment visível/interativo | Iniciar coleta/atualizações que dependem de visibilidade |
onPause/onStop | Perde foco/não visível | Pausar atualizações |
onDestroyView | A view será destruída | Limpar referências a view/binding |
onDestroy | Fragment destruído | Limpeza 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
onStartouonResume - Remova em
onStopouonPause
3) Coroutines: prefira escopos corretos
- Em Activity:
lifecycleScopeé adequado para tarefas ligadas à tela. - Em Fragment: para UI, prefira
viewLifecycleOwner.lifecycleScope. - Evite
GlobalScopepara 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ódigo | Activity | Fragment |
|---|---|---|
| Inflar layout / binding | onCreate | onCreateView/onViewCreated |
| Configurar listeners de UI | onCreate (após setContentView) | onViewCreated |
| Coletar Flow para atualizar UI | lifecycleScope + repeatOnLifecycle | viewLifecycleOwner.lifecycleScope + repeatOnLifecycle |
| Registrar/desregistrar recursos (sensores, localização, câmera) | onResume/onPause ou onStart/onStop | onResume/onPause ou onStart/onStop |
| Limpar referência de view/binding | Não se aplica (view vive com a Activity) | onDestroyView |