App Travando Após Atualização: Desmontando e Resolvendo o Bug
Descubra como identificar e resolver cada causa de travamentos após atualizações de apps: migração de dados, dependências, memory leaks e mais.
Você acabou de lançar uma atualização do seu app mobile e os relatos de travamentos começam a chegar. O cenário é familiar para qualquer desenvolvedor: usuários frustrados, avaliações negativas na loja e uma equipe em modo emergência tentando descobrir o que deu errado.
O problema não é simples como parece. Um app que trava após atualização raramente tem uma única causa — é um emaranhado de questões que vão desde compatibilidade até má gestão de recursos. Vamos desmontar este problema em suas partes fundamentais e resolver cada uma sistematicamente.
Parte 1: Incompatibilidade com Versões Antigas de Dados
A primeira e mais comum armadilha está na migração de dados. Seu app armazena informações localmente — seja em SQLite, Realm, SharedPreferences ou arquivos JSON. Quando você atualiza o esquema desses dados sem criar uma estratégia de migração adequada, o app simplesmente quebra ao tentar ler estruturas antigas com código novo.
Como resolver:
- Implemente versionamento de schema: Toda mudança no banco de dados local deve incrementar um número de versão. No SQLite, use o método
onUpgrade()para Android ou migrations no Core Data para iOS. - Mantenha retrocompatibilidade: Ao adicionar novos campos, sempre forneça valores padrão. Nunca assuma que todos os usuários começam do zero.
- Teste com dados reais: Antes de lançar, instale a versão antiga do app, use-o normalmente, e só então atualize. Os dados devem migrar sem erros.
- Implemente fallback: Se a migração falhar, tenha um plano B — pode ser resetar apenas aquela tabela específica ou usar dados da nuvem.
Parte 2: Dependências e Bibliotecas em Conflito
Você atualizou uma biblioteca de terceiros para corrigir um bug de segurança. Ótimo. Mas essa nova versão pode ter mudado APIs, alterado comportamentos padrão ou criado conflitos com outras dependências do projeto.
No ecossistema Android, isso é especialmente problemático com as bibliotecas do Jetpack e Google Play Services. No iOS, atualizações de CocoaPods podem introduzir versões incompatíveis entre si.
Como resolver:
- Audite o dependency tree: Use
./gradlew dependenciesno Android oupod install --verboseno iOS para visualizar toda a árvore de dependências e identificar conflitos. - Force versões específicas: Quando duas bibliotecas exigem versões diferentes da mesma dependência, force uma versão compatível explicitamente no arquivo de configuração.
- Leia changelogs religiosamente: Antes de atualizar qualquer biblioteca, leia as notas de versão procurando por breaking changes e deprecated methods.
- Atualize gradualmente: Nunca atualize todas as dependências de uma vez. Faça isso em pequenos lotes e teste entre cada atualização.
Parte 3: Memory Leaks Agravados
Memory leaks existem em praticamente todos os apps, mas pequenos vazamentos podem passar despercebidos na versão anterior. Uma atualização que adiciona novas features ou refatora código pode agravar esses leaks a ponto de causar crashes por falta de memória.
Referências a contextos no Android, delegates não liberados no iOS, listeners não removidos — tudo isso acumula memória até o sistema operacional matar o processo.
Como resolver:
- Use ferramentas de profiling: Android Profiler e Xcode Instruments são essenciais. Execute testes de longa duração e observe o gráfico de memória — ele deve ter padrão de serra (sobe e desce), não crescimento constante.
- Audite listeners e callbacks: Todo listener registrado deve ter um unregister correspondente. Use WeakReferences quando apropriado.
- Cuidado com singletons: Objetos que vivem durante todo o ciclo do app não devem guardar referências a Activities, ViewControllers ou outros objetos de curta duração.
- Implemente canary builds: Crie builds especiais com ferramentas como LeakCanary (Android) rodando continuamente durante o desenvolvimento.
Parte 4: Threading e Operações Assíncronas Mal Gerenciadas
Atualizações frequentemente introduzem novas chamadas de API, processamento de imagens ou operações de I/O. Se essas tarefas rodam na thread principal, o app congela. Se são mal sincronizadas em threads secundárias, causam race conditions e crashes imprevisíveis.
Como resolver:
- Nunca bloqueie a main thread: Operações de rede, acesso a disco e processamento pesado devem sempre rodar em background. Use Coroutines (Kotlin), async/await (Swift) ou RxJava/RxSwift.
- Sincronize acesso a recursos compartilhados: Se múltiplas threads acessam a mesma variável ou estrutura de dados, use locks, mutexes ou estruturas thread-safe.
- Cancele operações em andamento: Quando um usuário sai de uma tela, cancele todas as operações assíncronas relacionadas para evitar tentar atualizar UI que não existe mais.
- Monitore ANRs e crashes: Firebase Crashlytics, Sentry ou Bugsnag mostram exatamente onde o app está travando, incluindo stack traces completos.
Parte 5: Problemas Específicos de Plataforma e Versão do SO
Seu app pode funcionar perfeitamente no Android 13 e travar no Android 11. Ou rodar bem no iOS 16 mas quebrar no iOS 14. Cada versão do sistema operacional tem quirks, bugs próprios e comportamentos diferentes.
Como resolver:
- Teste em dispositivos reais: Emuladores não capturam todos os problemas. Tenha uma coleção de dispositivos físicos com diferentes versões de OS.
- Use análise de crash por OS version: Ferramentas de analytics mostram se crashes estão concentrados em versões específicas do sistema.
- Implemente feature flags: Se uma funcionalidade nova está causando problemas em versões antigas, desabilite-a remotamente apenas para aquelas versões.
- Respeite as mudanças de permissões: Android e iOS constantemente mudam como permissões funcionam. Cada atualização de OS pode exigir ajustes no seu código.
Estratégia de Rollout Defensivo
A melhor forma de evitar que uma atualização problemática afete todos os usuários é usar staged rollouts. Tanto Google Play quanto App Store permitem liberar atualizações gradualmente — comece com 5% dos usuários, monitore por 24-48 horas, e só então expanda.
Combine isso com feature flags remotas. Mesmo que o código esteja no app, você pode ativar ou desativar funcionalidades sem precisar enviar uma nova versão. Isso dá controle cirúrgico sobre o que cada usuário vê.
Checklist Pré-Atualização
Antes de enviar qualquer atualização para produção, garanta que você:
- Testou a migração de dados de pelo menos 3 versões anteriores
- Rodou testes automatizados de UI cobrindo os fluxos principais
- Verificou memory profiling em sessões de 30+ minutos
- Testou em dispositivos reais com diferentes versões de OS
- Configurou monitoramento de crashes com alertas automáticos
- Preparou um plano de rollback caso algo dê errado
Apps que travam após atualizações não são inevitáveis — são evitáveis com processos adequados. Desmontando o problema em partes gerenciáveis e atacando cada uma metodicamente, você transforma um caos reativo em prevenção sistemática.
Foto de capa: Vitaly Gariev via Unsplash