Vazamentos de memória e correções
Encontre e interrompa vazamentos.
Vazamentos de memória e correções é uma aula grátis de Android Academy no CoddyKit. Esta é a aula 3 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de Android Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de Android Academy inclui 4 aulas no total.
O que é um vazamento de memória?
Um vazamento de memória acontece quando objetos que não são mais necessários não podem ser coletados pelo coletor de lixo porque algo ainda mantém uma referência a eles. No Android, a vítima clássica é uma Activity ou um Context que permanece após a destruição da tela.
Os vazamentos aumentam sua pilha ao longo do tempo, provocam coletas de lixo mais frequentes (que pausam a interface e causam engasgos) e, por fim, fazem o aplicativo travar com um OutOfMemoryError. Nesta lição, você verá como encontrá-los e corrigi-los.
Como o GC decide o que manter
O coletor de lixo do Android mantém qualquer objeto que seja alcançável a partir de uma raiz do GC (threads ativas, campos estáticos etc.). Tudo o que não for alcançável é liberado.
Um vazamento é simplesmente um caminho de alcançabilidade indesejado: um objeto de longa duração que mantém uma referência a um objeto de curta duração. A demonstração em Kotlin puro abaixo mostra como a alcançabilidade mantém um objeto vivo.
object GlobalCache {
val items = mutableListOf<ByteArray>()
}
fun cacheSomething() {
// This 1MB array stays alive forever because GlobalCache
// (a static singleton, a GC root) keeps referencing it.
GlobalCache.items.add(ByteArray(1_000_000))
}
fun main() {
repeat(3) { cacheSomething() }
println("Held arrays: ${GlobalCache.items.size}") // never freed
}O clássico: vazamento de uma atividade
O vazamento mais comum no Android ocorre quando um objeto estático ou de longa duração mantém uma referência a um contexto. Quando a atividade é destruída (por rotação ou navegação), o sistema tenta liberá-la, mas a referência estática a mantém viva para sempre.
O código abaixo causa um vazamento: um singleton armazena em cache o contexto de uma atividade.
// LEAK: static field holds an Activity Context
object Analytics {
var context: Context? = null // <- keeps Activity alive
fun init(ctx: Context) { context = ctx }
}
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Analytics.init(this) // passing the Activity -> leak on rotation
}
}Correção: use o contexto da aplicação
Quando precisar armazenar um Context em algo de longa duração, armazene o contexto da aplicação, que permanece ativo durante todo o processo e pode ser mantido com segurança.
Mantenha um contexto de atividade ou de visualização somente enquanto essa tela existir. Essa regra simples evita a maioria dos vazamentos no Android.
object Analytics {
private var appContext: Context? = null
fun init(ctx: Context) {
// applicationContext is process-scoped and safe to keep
appContext = ctx.applicationContext
}
}
// Call site is now leak-free:
Analytics.init(this) // stores applicationContext, not the ActivityClasses internas e manipuladores
Uma classe interna não estática (incluindo a maioria dos ouvintes, dos Runnables e das chamadas de retorno de Handler) mantém uma referência implícita à classe externa. Se esse trabalho continuar depois que a tela for encerrada, ele causará um vazamento da tela.
Um Handler.postDelayed atrasado é um dos responsáveis mais frequentes: a mensagem pendente mantém a atividade viva até ser executada.
// LEAK: the posted Runnable holds the Activity for 60 seconds
handler.postDelayed({ updateUi() }, 60_000)
// FIX: cancel pending work when the screen goes away
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacksAndMessages(null)
}Corrotinas: defina o escopo do trabalho
Uma corrotina que continua ativa depois que a tela é encerrada causa o vazamento de tudo o que captura. A solução é a concorrência estruturada: inicie o trabalho em um escopo vinculado a um ciclo de vida para que ele seja cancelado automaticamente.
Em um ViewModel, use viewModelScope; em um controlador de interface, use lifecycleScope. Quando o proprietário é destruído, o escopo cancela o trabalho e as referências são liberadas.
class FeedViewModel : ViewModel() {
fun load() {
// Cancelled automatically when the ViewModel is cleared
viewModelScope.launch {
val feed = repository.fetchFeed()
_state.value = feed
}
}
}
// In Compose, collect tied to the lifecycle:
val state by viewModel.state.collectAsStateWithLifecycle()DisposableEffect no Compose
No Compose, tudo o que você registra deve ser desregistrado quando o componente composable sair da composição. DisposableEffect fornece um bloco onDispose exatamente para isso: ouvintes, observadores, sensores e receptores de transmissão.
Esquecer a limpeza nesse ponto causa o vazamento da chamada de retorno e de tudo o que ela captura.
@Composable
fun LocationDisplay(manager: LocationManager) {
DisposableEffect(manager) {
val listener = LocationListener { /* update */ }
manager.register(listener)
onDispose { manager.unregister(listener) } // prevents the leak
}
}Como encontrar vazamentos com LeakCanary
LeakCanary é a ferramenta padrão para detectar vazamentos automaticamente em compilações de depuração. Adicione a dependência, e ela observará os objetos destruídos; se algum não for coletado pelo coletor de lixo, ela fará uma captura do monte de memória e mostrará a cadeia exata de referências que o mantém vivo.
Essa cadeia indica a correção: ela aponta diretamente para o campo ou ouvinte problemático.
// build.gradle.kts (app module)
dependencies {
debugImplementation("com.squareup.leakcanary:leakcanary-android:2.14")
}
// No code needed: on a debug build, navigate away from a screen and
// LeakCanary posts a notification with the leak trace, e.g.:
// Analytics.context -> MainActivity (leaked)Capturas do monte de memória no criador de perfis
Para vazamentos que o LeakCanary não sinaliza (crescimento lento, código nativo ou caches grandes), use o criador de perfis de memória. Capture o monte de memória, force a coleta de lixo e verifique quais classes têm uma quantidade surpreendentemente alta de instâncias.
Uma quantidade crescente de instâncias da sua atividade, fragmento ou ViewModel depois de sair da tela é uma evidência clara. O criador de perfis também mostra o caminho até as raízes da coleta de lixo, para que você possa rastrear a referência.
Bitmaps e alocações grandes
Nem todo problema de memória é um vazamento de referências. Bitmaps grandes e caches sem limite podem esgotar o monte de memória por conta própria. Uma foto em resolução máxima pode ocupar dezenas de megabytes na memória.
Permita que uma biblioteca de imagens (Coil/Glide) reduza a amostragem para o tamanho exibido e defina um limite explícito para qualquer cache que criar. O trecho mostra um cache LRU limitado por tamanho.
// Bound memory: keep at most ~1/8 of available app memory
val maxKb = (Runtime.getRuntime().maxMemory() / 1024 / 8).toInt()
val bitmapCache = object : LruCache<String, Bitmap>(maxKb) {
override fun sizeOf(key: String, value: Bitmap) = value.byteCount / 1024
}
// An unbounded HashMap<String, Bitmap> would grow until OutOfMemoryError.Lista de verificação para evitar vazamentos
Adote estes hábitos para que a maioria dos vazamentos nunca aconteça:
- Armazene applicationContext em objetos de longa duração, nunca uma atividade ou visualização.
- Execute trabalhos assíncronos em uma corrotina com escopo de ciclo de vida (
viewModelScope,lifecycleScope). - Sempre desregistre ouvintes e receptores (
onDispose,onDestroy). - Cancele sempre os Handlers atrasados e os temporizadores.
- Defina limites para os caches e reduza a amostragem dos bitmaps.
- Mantenha o LeakCanary nas compilações de depuração e aja com base nos rastreamentos.
Verificação rápida
Você precisa que um objeto singleton mantenha um Context durante todo o seu tempo de vida. Qual contexto ele deve manter para evitar o vazamento de uma tela?
Recapitulação: mantenha a memória limpa
Você aprendeu o que são vazamentos e como impedi-los:
- Vazamentos são caminhos indesejados de alcançabilidade que mantêm objetos mortos vivos.
- O vazamento clássico é uma referência estática ou de longa duração a uma atividade ou contexto — armazene applicationContext no lugar.
- Ouvintes de classes internas, Handlers atrasados e corrotinas sem escopo causam o vazamento do proprietário — defina o escopo e cancele-os.
- Use
DisposableEffectno Compose para desregistrar chamadas de retorno. - LeakCanary e o criador de perfis de memória revelam a cadeia de referências.
- Defina limites para os caches e reduza a amostragem dos bitmaps.
A seguir: como fazer o aplicativo iniciar rapidamente com otimização da inicialização e perfis de referência.
Perguntas Frequentes
A aula “Vazamentos de memória e correções” é grátis?
Sim — o texto completo de “Vazamentos de memória e correções” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de Android Academy, atualize para CoddyKit PRO. O curso de Android Academy inclui 4 aulas no total.
O que vou aprender em “Vazamentos de memória e correções”?
Encontre e interrompa vazamentos. Você pratica Android Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar Android Academy?
Nenhuma experiência prévia é necessária. Android Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 3 de 4.
Quanto tempo leva a aula “Vazamentos de memória e correções”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de Android Academy?
Sim. Cada aula de Android Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- Medindo o desempenho
- Dominando a recomposição
- Vazamentos de memória e correções
- Inicialização e perfis de referência