Postado por Alice Yuan, Engenheira de Relações com Desenvolvedores, Ajesh Pai, Engenheiro de Relações com Desenvolvedores e Fung Lam, Engenheiro de Relações com Desenvolvedores
Embora o desempenho do aplicativo seja frequentemente associado a uma interface gráfica suave e tempos de inicialização rápidos, a memória serve como a base silenciosa sobre a qual essas métricas visíveis são construídas. Não é segredo que estamos vendo uma mudança onde a memória do dispositivo é mais importante do que nunca. Além dos avanços em otimizações de memória do Android com o Android 17, estamos fornecendo ferramentas e suporte à API para ajudá-lo a se manter à frente das exigências mais rigorosas de memória no decorrer deste ano.
Para garantir a estabilidade do dispositivo, a partir do Android 17, o sistema começará a impor limites de memória do aplicativo com base na RAM total do dispositivo. Se um aplicativo exceder esses limites, o Android matará o processo sem nenhum rastreamento de pilha associado.
Além dessas terminações forçadas, o uso não otimizado da memória inevitavelmente deteriora a experiência do usuário. Quando o aplicativo se aproxima dos limites da memória do heap, isso aciona coleta de lixo frequente - levando a estremecimentos visíveis na interface gráfica. Além disso, quando um dispositivo esgota a memória disponível, o sistema luta para recuperar páginas, causando sobrecarga no CPU, latência na interface gráfica e drenagem da bateria. Se a escassez de memória for muito severa, pode causar eventos do Killer de Memória Baixa (LMK), que abruptamente terminam processos em segundo plano e fazem com que os aplicativos tenham inicializações frias lentas e percam o estado do usuário.
Uma versão condensada deste post de blog também está disponível no formato de vídeo, confira!
Entendendo os limites de memória do aplicativo no Android 17
Limites de memória para aplicativos estão sendo introduzidos no Android 17 para prevenir um único aplicativo malicioso de destruir a experiência de multitarefa e estabilidade do dispositivo inteiro do usuário.
Aqui está uma análise das razões que impulsionam essa mudança arquitetônica:
- Prevenindo mortes em cascata: Quando um aplicativo se torna inchado ou vaza memória enquanto mantém um estado privilegiado (por exemplo, está executando um Serviço de Fundo), inicialmente é protegido do Killer de Memória Baixa (LMK) do sistema. À medida que este único aplicativo cresce sem controle e apropria-se da RAM, o LMK é forçado a compensar matando dezenas de pequenos aplicativos em cache e trabalhos de fundo para recuperar espaço para o consumidor de memória.
- Preservando multitarefas e estado do usuário: Quando o sistema é obrigado a purgar aplicativos em cache para acomodar um único processo vazador, a experiência de multitarefa é severamente comprometida. Os usuários que retornam a aplicativos anteriormente em cache enfrentam iniciações frias lentas ao invés de resumos quentes próximos à instantaneidade. Essa ineficiência gera mais estresse no CPU e acelera o esgotamento da bateria. Também pode destruir o contexto do usuário em aplicativos recentemente usados, como posições de rolagem, pilhas de navegação e progresso em jogos.
Para determinar se a sessão do seu aplicativo foi impactada por essas restrições no campo, você pode chamar getDescription() dentro de ApplicationExitInfo. Se o sistema aplicou um limite, a razão de saída é relatada como REASON_OTHER e a string da descrição conterá "MemoryLimiter:AnonSwap". Você também pode aproveitar o perfilamento baseado em gatilho usando TRIGGER_TYPE_ANOMALY para capturar automaticamente dump de pilha quando o limite de memória é atingido. Além disso, o Android está trabalhando ativamente para expor mais métricas de memória em campo aos desenvolvedores dentro do Google Play Console.
Também expandimos nossa documentação sobre limites de memória para incluir comandos de depuração local, permitindo que você simule restrições de memória em seu ambiente local e valide o comportamento do aplicativo sob qualquer enforcamento de limite de memória.
Máximo otimização de bytecode com R8
Uma maneira altamente eficaz de reduzir o footprint de memória do seu aplicativo é habilitar o otimizador R8. Ao encurtar classes, métodos e campos em nomes mais curtos e removendo código e recursos não utilizados, o R8 reduz significativamente o footprint de memória do seu aplicativo minimizando a quantidade de código residente necessário durante a execução.
O R8 minimiza o código residente, encurtando o footprint de memória e diminuindo o risco de terminação pelo LMK. Isso resulta em inícios quentes mais frequentes do que iniciações frias lentas. Além disso, bytecode otimizado reduz a sobrecarga de CPU na thread principal, cortando diretamente as taxas ANR para uma experiência de usuário mais fluida. Por exemplo, o banco digital Monzo habilitou a otimização completa do R8 e viu uma redução de 35% em suas taxas ANR, uma melhoria de 30% na taxa de iniciações frias e uma redução de 9% no tamanho geral do aplicativo.
Para configurar corretamente o R8 no seu arquivo build.gradle:
- Defina
isShrinkResources = trueeisMinifyEnabled = true. - Use
proguard-android-optimize.txtem vez do legadoproguard-android.txt, que na verdade impede otimizações e não é mais suportado no Android Gradle Plugin 9. - Remova
android.enableR8.fullMode = falsede seu arquivogradle.properties.
Se você está usando reflexão em sua base de código, adicione regras de manutenção para impedir que o R8 otimize essas partes do código. Certifique-se de escopar as regras de manutenção com precisão para obter a maior otimização.
Para obter a máxima otimização, certifique-se de seguir estas melhores práticas em seu arquivo de regras de manutenção.
- Remova opções globais como
-dontoptimize,-dontshrinke-dontobfuscateque impedem o R8 de otimizar toda a base de código - Remova regras de manutenção que impedem a otimização de componentes Android como Activity, Services, Views ou Broadcast receivers.
- Aperfeiçoe as regras de manutenção amplas do pacote para alvo apenas classes ou métodos específicos.
Para ver mais práticas recomendadas, consulte nossa documentação de regras de manutenção.
Melhores Práticas para Desenvolvedores de Bibliotecas R8
Se você é um desenvolvedor de biblioteca, coloque estritamente as regras que seus consumidores precisam em seu arquivo consumer-rules, e mantenha as regras internas de proteção da sua biblioteca no arquivo proguard-rules.pro. Para obter mais informações sobre como otimizar bibliotecas, consulte Otimização para autores de bibliotecas.
Analisador de Configuração R8
Para auditar sua otimização do R8, use o Analisador de Configuração. O Analisador de configuração mostra o estado atual da otimização com pontuações de Obfuscation, Optimization e Shrinking. Com o analisador de configuração, você também pode entender quantas classes, métodos ou campos são impedidos de serem otimizados por cada regra de manutenção. Aperfeiçoe essas regras amplas do pacote para liberar a máxima otimização.
Usando o analisador de configuração, você também pode identificar regras de manutenção que estão subsumindo outras regras de manutenção, regras de manutenção redundantes e regras de manutenção não utilizadas.
O Analisador de Configuração mostra o estado atual da otimização com pontuações de Obfuscation, Optimization e Shrinking.
Habilidade do Agente R8
Você também pode aproveitar a Habilidade do Agente R8 com o agente do Android Studio ou outras ferramentas de IA para resolver configurações incorretas e refinar suas regras, resultando em melhor desempenho do aplicativo. (Insights das habilidades impulsionadas por IA exigirão verificação técnica)
Otimizar o carregamento de imagens
Bitmaps geralmente são os objetos mais comuns e grandes residindo na memória do seu aplicativo. Eles representam a etapa final do processo de carregamento de imagens onde arquivos comprimidos, como JPEG ou PNG, são decodificados em dados brutos de pixels para exibição. Isso significa que uma pequena imagem comprimida de 100KB pode se expandir em vários megabytes de RAM porque o consumo de memória é determinado pelas dimensões dos pixels da imagem e pela profundidade de cor. Como as operações com bitmaps frequentemente estão no caminho crítico para desenhar quadros, imagens não otimizadas causam um aumento severo na utilização de memória e problemas de desempenho na interface do usuário.
A Google recomenda a utilização de bibliotecas de carregamento de imagens Coil para projetos baseados em Kotlin, especialmente ao desenvolver com Jetpack Compose e Glide para aplicações baseadas em Java.
Adequar essas cinco melhores práticas
- Redimensione imagens: Se você estiver carregando bitmaps manualmente, evite carregar uma imagem enorme em um pequeno visualizador de miniaturas; use inSampleSize para carregar uma versão menor. Glide e Coil redimensionam imagens por padrão e você pode configurar essa estratégia de redimensionamento usando DownsampleStrategy e ImageLoader, respectivamente.
- Corte: Evite incorporar espaços em branco diretamente no arquivo de imagem para fins de letterboxing (por exemplo, criar uma borda transparente para expandir as dimensões da imagem). Em vez disso, use InsetDrawable ou aplique espaçamento diretamente no View ou Composable que contém o bitmap.
- Configuração: Equilibre memória e qualidade escolhendo a formatação de pixel correta. Use
RGB_565quando não for necessário transparência, pois isso usa metade da memória do formato padrãoARGB_8888. Em Glide você pode configurar isso usando DecodeFormat e em Coil você pode usar a propriedade bitmapConfig. - Priorize drawables vetoriais: Para ativos geométricos básicos, aproveite ShapeDrawable como uma alternativa leve à decodificação de bitmaps rasterizados. Ao definir esses ativos uma vez via XML, você garante que eles escalonem perfeitamente em todas as densidades de exibição enquanto efetivamente elimina a inchação de recursos na memória.
- Reutilize: Se seu aplicativo gerencia manualmente Bitmaps, para minimizar o churn de memória, quando um bitmap não for mais necessário, o aplicativo deve chamar
bitmap.recycle()e descartar imediatamente a referência ao Bitmap. Se você usar uma biblioteca de carregamento de imagens como Glide ou Coil, devolva o bitmap para a piscina gerenciada pela biblioteca. Ao fornecer um buffer existente para futuras necessidades de memória, a piscina efetivamente evita a sobrecarga de novas alocações.
Confira nossa documentação sobre Otimizando o desempenho para imagens para saber mais.
Ferramentas do Android Studio
Você também pode eliminar bitmaps redundantes usando o Android Studio Narwhal 4. Aqui está como encontrá-los em cinco passos simples:
- Abrir a aba Profiler no Android Studio
- Clique em Heap Dump (ou "Analisar Uso de Memória") e pressione gravar para capturar um instantâneo do estado atual da memória do seu aplicativo.
- Pesquisar os resultados da análise pelo triângulo amarelo de aviso ⚠️, que o Android Studio usa para marcar bitmaps duplicados armazenados múltiplas vezes. Alternativamente, navegue até a barra superior do perfilador e escolha "Filtrar por:" e selecione a opção "Bitmaps Duplicados".
- Clique em qualquer entrada marcada para abrir o Visualizador de Bitmap, permitindo que você veja exatamente qual imagem é o culpado repetido.
- Use essa confirmação visual para rastrear a lógica redundante de carregamento no seu código e implementar uma melhor estratégia de cache.
Procure pelo triângulo amarelo de aviso ⚠️ nos dump de pilha ao usar o Profiler do Android Studio.
Detectar e corrigir vazamentos de memória com o Android Studio
Vazamentos de memória no Android ocorrem quando seu código mantém uma referência a um objeto por muito tempo após o fim de sua vida útil. Isso impede que o Garbage Collector (GC) reivindique essa memória, levando eventualmente a desempenho lento ou OutOfMemoryError (OOM).
O Android Studio Panda 3 possui uma tarefa dedicada do LeakCanary, permitindo que os desenvolvedores analisem vazamentos de memória em tempo real e mapeiem rastros dentro da IDE.
A tarefa do perfilador LeakCanary no Android Studio move ativamente a análise de vazamento de memória do seu dispositivo para sua máquina de desenvolvimento, resultando em um aumento significativo de desempenho durante a fase de análise de vazamentos em comparação com a análise de vazamentos no próprio dispositivo.
Análise de vazamento de memória LeakCanary contextualizada com Ir para declaração para depuração
A análise de vazamento agora está contextualizada dentro da IDE e totalmente integrada ao seu código-fonte, fornecendo recursos como ir para declaração e outras conexões úteis do código que drasticamente reduzem a fricção e o tempo necessário para investigar e corrigir vazamentos de memória.
Exemplos de vazamentos comuns de memória
Vazamentos de memória ocorrem quando um objeto persiste na memória além de sua vida útil intencionada. Isso geralmente acontece por:
- Mantendo referências a Fragments, Activities ou Views que não são mais em uso.
- Gerenciando incorretamente as referências ao Contexto.
- Não desregistrando corretamente observadores, listeners e receivers.
- Criando referências estáticas a objetos vinculados a componentes com ciclos de vida mais curtos.
Aqui estão alguns cenários de exemplo:
Cenário |
Exemplo baseado em Compose |
Exemplo baseado em View |
Contexto Vazando |
Exemplo de vazamento de contexto:
Passando LocalContext.current para um ViewModel
Correção:
Manter a lógica dependente de Context dentro da camada UI. Para as camadas não-UI, refatorar para usar injeção de dependência ou observar o estado da IU usando Kotlin flow.
Exemplo:
Armazenando um Activity em um objeto companion ou variável estática.
Correção:
Não mantenha referências estáticas a componentes de IU. Refatorar para usar injeção de dependência ou observar o estado da IU usando Kotlin flow.
Vazamento de Listeners
Exemplo:
Usando DisposableEffect para iniciar um listener, mas deixando o bloco onDispose vazio.
Correção:
Realizar a desassociação e a limpeza dentro do bloco onDispose.
Exemplo:
Registrando para atualizações do SensorManager e esquecendo de desassociar.
Correção:
Chamar manualmente o método unregisterListener() nos métodos do ciclo de vida onStop() ou onDestroy().
Vazamento de Views
Exemplo:
Manter uma referência a um legado View dentro de um AndroidView sem estratégia de liberação.
Correção:
Usar o bloco release do composable AndroidView para limpar o legado View.
Exemplo:
Manter uma referência ao objeto de vinculação de visualização após a destruição do Fragment.
Correção:
Definir a variável de vinculação para null dentro do método do ciclo de vida onDestroyView().
Reduza a memória quando o aplicativo sai do estado visível
O Android pode recuperar memória do seu aplicativo ou interromper completamente o aplicativo se necessário para liberar espaço de memória para tarefas críticas, conforme explicado em Visão geral da gestão de memória. O Android geralmente recuperará a memória do seu aplicativo quando este não estiver visível ao usuário, como por exemplo descartando parte do código e páginas de dados do seu aplicativo ou comprimindo suas alocações na pilha. Quando o usuário retoma o seu aplicativo e o aplicativo tenta acessar alguma memória que foi recuperada, o sistema operacional substituirá essa memória sob demanda. Este comportamento de troca pode ser lento e causar jank ou estalos inesperados no seu aplicativo.
Se você deixar que o SO decida qual memória recuperar do seu aplicativo, é possível que o OS tenha recuperado a memória que você precisará logo após retomar o seu aplicativo. Em vez disso, o seu aplicativo pode voluntariamente descartar alocações de memória que podem ser regeneradas posteriormente, sob demanda e com baixo custo. Para fazer isso, você pode implementar a interface ComponentCallbacks2. Você pode implementar onTrimMemory em seu Activity, Fragment, Service ou até mesmo na sua classe personalizada Application. Usá-lo na classe Application é altamente eficaz para a gestão de cache global.
O método de retorno de chamada fornecido onTrimMemory() notifica o seu aplicativo sobre eventos do ciclo de vida ou relacionados à memória que apresentam uma boa oportunidade para que o seu aplicativo reduza voluntariamente o uso da sua memória.
No gerenciamento do ciclo de vida da memória, a implementação deve focar exclusivamente em TRIM_MEMORY_UI_HIDDEN e TRIM_MEMORY_BACKGROUND. Desde o Android 14, o sistema deixou de enviar notificações para outras constantes legadas, que foram formalmente depreciadas no Android 15.
TRIM_MEMORY_UI_HIDDEN: Esta sinalização indica que a interface do seu aplicativo foi transicionada fora da visão do usuário. Isso oferece uma oportunidade para liberar grandes alocações de memória vinculadas estritamente à interface, como Bitmaps, buffers de reprodução de vídeo ou recursos complexos de animação.
TRIM_MEMORY_BACKGROUND: Neste nível, o seu processo está residindo em segundo plano e agora é um candidato para terminar para atender às necessidades globais do sistema. Para prolongar a duração que o seu processo permanece no estado cacheado e reduzir o número de inicializações frias do aplicativo, você deve agressivamente liberar quaisquer recursos que possam ser facilmente reconstruídos uma vez que o usuário retome sua sessão.
import android.content.ComponentCallbacks2
// Outras declarações de importação.
class MainActivity : AppCompatActivity(), ComponentCallbacks2 {
/**
* Libere a memória quando a interface do usuário se torna oculta ou quando os recursos do sistema ficam baixos.
* @param level o evento relacionado à memória que é levantado.
*/
override fun onTrimMemory(level: Int) {
if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
// Libere a memória relacionada aos elementos de interface do usuário, como caches de bitmap.
}
if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
// Libere a memória relacionada ao processamento em segundo plano, como por exemplo
// fechando uma conexão com o banco de dados.
}
}
}
Nota: A integração onTrimMemory pode depender do suporte da SDK. Por exemplo, certos jogos dependem de sua engine de jogo para habilitar essa capacidade. Consulte os documentos sobre otimização de memória para jogos.
Observabilidade avançada com ProfilingManager
Para capturar e diagnosticar problemas de memória em campo que não podem ser reproduzidos localmente, você deve aproveitar a API ProfilingManager. Introduzida no Android 15, esta API de observabilidade avançada permite coletar programaticamente perfis do Perfetto dos usuários reais.
Para equipes que não possuem uma infraestrutura dedicada para gerenciar e hospedar artefatos de desempenho, a Crashlytics está explorando uma solução especializada para otimizar este fluxo de trabalho. Eles estão convidando desenvolvedores a fornecer feedback.
O Android 17 introduz novos gatilhos baseados em eventos, notavelmente TRIGGER_TYPE_OOM e TRIGGER_TYPE_ANOMALY:
- O gatilho OOM coleta automaticamente um dump de heap Java no exato momento em que ocorre uma falha OutOfMemoryError, fornecendo estados de alocação precisos. Um perfil OOM coletado é fornecido na próxima vez que o aplicativo inicia e registra a chamada de retorno
registerForAllProfilingResults. - O gatilho Anomalia detecta problemas graves de desempenho, como spam excessivo do binder ou limiares de memória excedidos. A anomalia de memória entrega um dump de heap imediatamente antes que o sistema interrompa o aplicativo.
val profilingManager =
applicationContext.getSystemService(ProfilingManager::class.java)
val triggers = ArrayList<profilingtrigger>()
triggers.add(ProfilingTrigger.Builder(
ProfilingTrigger.TRIGGER_TYPE_ANOMALY))
val mainExecutor: Executor = Executors.newSingleThreadExecutor()
val resultCallback = Consumer<profilingresult> { profilingResult ->
if (profilingResult.errorCode != ProfilingResult.ERROR_NONE) {
// upload profile result to server for further analysis
setupProfileUploadWorker(profilingResult.resultFilePath)
}
profilingManager.registerForAllProfilingResults(mainExecutor, resultCallback)
profilingManager.addProfilingTriggers(triggers)
Uma vez que você tenha coletado o dump de heap, pode baixar o perfil do servidor ou localmente via adb pull e arrastar e soltar o arquivo no Perfetto UI. Para otimizar seu fluxo de trabalho de depuração de memória, use o Heap Dump Explorer, esta é a nova visualização padrão para dumps de heap no Perfetto UI. Esta ferramenta fornece uma interface intuitiva para inspecionar dumps de heap Java, permitindo que você visualize hierarquias de alocação de objetos, calcule tamanhos de memória reteridos e identifique o caminho mais curto a partir de raízes de coleta de lixo. Ao aproveitar o Heap Dump Explorer, você pode rapidamente localizar vazamentos de memória, objetos reteridos inchados como alocações excessivas de bitmap e analisar todas as alocações de objetos da pilha em um único lugar.
Conclusão
Otimizar o bytecode com R8, adotar práticas recomendadas de carregamento de imagens e resolver vazamentos de memória são passos críticos para entregar uma experiência do usuário de alta qualidade enquanto gerencia efetivamente os recursos sob pressão. Adotar essas medidas proativas ajuda a manter a estabilidade e o desempenho do aplicativo, prevenindo terminações inesperadas enquanto protege o contexto do usuário. Para expandir seus conhecimentos sobre performance, explore nossa guia revisada de memória.

