Esta página explica como reduzir proativamente o uso da memória no seu app. Para informações sobre como o sistema operacional Android gerencia a memória, consulte Visão geral do gerenciamento de memória.
A memória de acesso aleatório (RAM) é um recurso valioso em qualquer ambiente de desenvolvimento de software, sendo ainda mais importante em um sistema operacional móvel em que a memória física costuma ser restrita. Embora o Android Runtime (ART)
e a máquina virtual Dalvik façam a coleta de lixo de rotina, isso não significa
que você pode ignorar quando e onde seu app aloca e libera memória. Ainda é necessário evitar introduzir vazamentos de memória, que geralmente ocorrem quando referências de
objetos são armazenadas em variáveis de membros estáticas, e liberar objetos Reference
no momento adequado, conforme definido pelos callbacks do ciclo de vida.
Reduzir o consumo de código e recursos do app
Alguns recursos e bibliotecas do código podem consumir memória sem que você perceba. O tamanho total do seu app, incluindo bibliotecas de terceiros ou recursos incorporados, pode afetar a quantidade de memória que ele consome. Você pode melhorar o consumo de memória do seu app removendo do código os componentes, recursos e bibliotecas redundantes, desnecessários ou pesados.
Reduzir o tamanho geral do app ativando o R8
O código do aplicativo compilado é uma parte ativa da sua pegada de memória de tempo de execução. Cada classe, método, dependência de biblioteca e constante de string precisa ser carregada na RAM quando executada. Quanto maior for sua base de código compilada, mais RAM física seu app vai precisar para existir.
Você pode usar o R8 para reduzir o consumo de memória do app. Embora o R8 seja tradicionalmente conhecido por reduzir o tamanho do APK, ele tem um impacto direto e positivo na memória de execução (RAM). O R8 analisa o bytecode do app para remover código morto, mesclar classes redundantes, métodos inline e reduzir identificadores. Ao carregar menos bytecode compilado do APK na RAM, ele diminui o consumo geral de memória de referência do app. Além disso, a minificação de nomes de classes, métodos e campos em identificadores mais curtos reduz diretamente a sobrecarga da RAM. Otimizações como a fusão de classes e a inserção de métodos extensiva também substituem pesquisas e padrões de alocação de tempo de execução caros, resultando em heap e memória de pilha otimizados.
Entender as regras de retenção
As regras de preservação são instruções de configuração que informam ao R8 quais partes do código preservar durante a otimização, evitando que ele remova ou reduza o código de que seu app depende. Para mais informações, consulte a Visão geral das regras de retenção.
Regras keep mal escritas impedem que o R8 otimize grandes partes da sua base de código. Evite regras de retenção muito abrangentes e siga estas práticas recomendadas:
- Regras globais a serem evitadas:
-dontoptimize: desativa completamente a otimização para todo o app, resultando em executáveis maiores e mais lentos.-dontshrink: impede a remoção de código e recursos não utilizados.-dontobfuscate: impede a minificação de nomes, perdendo uma economia de memória valiosa (especialmente em apps grandes).
Evite caracteres curinga em todo o pacote:regras amplas como
-keep class com.example.package.** { *; }forçam o R8 a preservar todas as classes, campos e métodos desse pacote. Isso interrompe completamente a capacidade do R8 de remover, otimizar ou reduzir o código nesse pacote.Use o arquivo de configuração padrão do R8:sempre use
proguard-android-optimize.txt.
Para mais informações sobre como escrever regras de retenção, consulte a Visão geral das regras de retenção. Para ver padrões específicos a serem usados e evitados, consulte Práticas recomendadas para regras de retenção.
O analisador de configuração do R8 fornece insights sobre sua configuração do R8 e como cada regra de manutenção afeta seu app. Para mais informações sobre como identificar regras que bloqueiam a otimização, consulte Analisador de configuração do R8.
Cuidado ao usar bibliotecas externas
Códigos de bibliotecas externas geralmente não são projetados para ambientes de dispositivos móveis e podem ser ineficientes para um cliente móvel. Esse tipo de biblioteca pode precisar ser otimizado para dispositivos móveis. Planeje esse trabalho com antecedência e analise a biblioteca em termos de tamanho de código e uso de recursos de RAM antes de usá-la.
Até mesmo algumas bibliotecas otimizadas para dispositivos móveis podem causar problemas devido a implementações diferentes. Por exemplo, uma biblioteca pode usar protobufs leves enquanto outra usa microprotobufs, resultando em duas implementações de buffers de protocolo diferentes no app. Isso pode acontecer com várias implementações de geração de registros, análise, frameworks de carregamento de imagens, armazenamento em cache e muitos outros fatores inesperados.
Embora a otimização do app usando o R8 possa remover código não utilizado das dependências, a eficácia dele geralmente é limitada pela configuração interna da biblioteca. Por exemplo, regras de manutenção amplas ou o uso de reflexão em uma biblioteca podem impedir que o R8 reduza o código, resultando em uma pegada de memória maior. Para estratégias sobre como selecionar bibliotecas eficientes, consulte Escolha bibliotecas com sabedoria.
Evite usar uma biblioteca compartilhada para apenas um ou dois recursos de dezenas. Não importe uma grande quantidade de código e sobrecarga que você não vai usar. Quando você considerar o uso de uma biblioteca, procure uma implementação que corresponda ao que você precisa. Caso contrário, você pode criar uma implementação própria.
Usar Hilt ou Dagger 2 para injeção de dependência
Frameworks de injeção de dependência podem simplificar o código que você escreve e fornecer um ambiente adaptável útil para testes e outras mudanças na configuração.
Se você pretende usar um framework de injeção de dependência no seu app, recomendamos usar Hilt ou Dagger. O Hilt é uma biblioteca de injeção de dependências para Android que é executada com base no Dagger. O Dagger não usa reflexão para verificar o código do app. Use a implementação de compilação estática da Dagger em apps Android sem custo desnecessário de tempo de execução ou uso da memória.
Outros frameworks de injeção de dependência que usam reflexão inicializam processos verificando o código em busca de anotações. Esse processo pode exigir significativamente mais ciclos de CPU e RAM, além de causar um atraso considerável quando o app é iniciado.
Ao usar a injeção de dependência, tenha cuidado para evitar vazamentos de memória. Para isso, garanta que os objetos estejam no escopo adequado. Manter objetos por mais tempo do que o necessário ao vinculá-los ao ciclo de vida errado pode causar vazamentos de memória. Para mais informações, consulte a orientação sobre como evitar vazamentos de memória com objetos de escopo.
Seja intencional com o carregamento de imagens
Os bitmaps gráficos geralmente são os maiores objetos comuns na memória do seu app. Mesmo que você esteja trabalhando com arquivos compactados, como JPEGs, o arquivo precisa ser convertido em um bitmap não compactado para ser exibido na tela. Um arquivo de imagem pequeno e compactado pode se expandir em um bitmap muito grande.
Por exemplo, a maioria dos bitmaps usa a configuração ARGB_8888, o que significa que cada pixel exige 4 bytes de memória: um byte para cada cor (vermelho, verde, azul) e um para o canal alfa (transparência). Se você tiver um JPEG de 100 KB e o exibir em uma visualização de 1.000 × 1.000 pixels, o bitmap vai exigir 4 bytes para cada um desses 1.000.000 pixels, adicionando até 4 MB de memória.
Há várias coisas que você pode fazer para otimizar o uso de imagens. Por exemplo, usar bibliotecas de carregamento de imagens pode ajudar a liberar memória quando ela não é necessária. Para saber como processar imagens de maneira eficiente, consulte Otimizar imagens de bitmap.
Monitorar a memória disponível e o uso de memória
Para corrigir os problemas de uso da memória do seu app, você precisa encontrá-los. O Memory Profiler do Android Studio ajuda a encontrar e diagnosticar problemas de memória das seguintes maneiras:
- Conferir como o app aloca memória ao longo do tempo. O Memory Profiler mostra um gráfico em tempo real de quanta memória seu app está usando, o número de objetos Java alocados e quando ocorre a coleta de lixo.
- Inicie os eventos de coleta de lixo e faça uma captura de tela do heap Java enquanto o app é executado.
- Registre as alocações de memória do app, inspecione todos os objetos alocados, confira o rastreamento de pilha para cada alocação e acesse o código correspondente no editor do Android Studio.
O Memory Profiler também se integra à biblioteca de detecção de vazamentos LeakCanary. Com o LeakCanary, é possível mover a análise de vazamento de memória do dispositivo de teste para a máquina de desenvolvimento, o que pode acelerar significativamente seu fluxo de trabalho. Para mais informações, consulte as notas da versão do Android Studio.
Há outras ferramentas que você pode usar para diagnosticar problemas de memória com base em dados de usuários que executam seu app de produção:
- Use o Android vitals para rastrear eventos de baixa memória (LMK).
- Use o Profiling Manager para rastrear erros de falta de memória e comportamento anômalo do app que pode ser causado por vazamentos de memória.
Liberar memória em resposta a eventos
O Android pode liberar a memória do app ou encerrá-lo totalmente, se necessário,
para reduzir o uso de memória e permitir a execução de tarefas essenciais, conforme explicado na Visão geral do gerenciamento de memória. Para equilibrar ainda mais a memória do sistema e evitar a necessidade de encerrar o processo do app, implemente a interface ComponentCallbacks2 nas classes Activity. O método de callback onTrimMemory() fornecido notifica seu app sobre eventos relacionados ao ciclo de vida ou à memória que representam uma boa oportunidade para o app reduzir voluntariamente o uso da memória.
Liberar memória pode reduzir a frequência com que o app é encerrado pelo
low-memory killer.
Sua implementação de onTrimMemory() deve se concentrar exclusivamente nos eventos
TRIM_MEMORY_UI_HIDDEN e TRIM_MEMORY_BACKGROUND. A partir do Android 14, o sistema não envia mais notificações para as outras constantes legadas. Essas constantes foram formalmente descontinuadas no Android 15.)
TRIM_MEMORY_UI_HIDDEN: esse indicador mostra que a interface do app saiu da visualização do usuário. Essa transição oferece uma oportunidade de liberar alocações de memória substanciais estritamente vinculadas à interface, como bitmaps, buffers de reprodução de vídeo ou recursos de animação complexos.TRIM_MEMORY_BACKGROUND: esse indicador mostra que seu processo está em segundo plano e agora é um candidato à interrupção para atender às necessidades globais de memória do sistema. Para estender a duração em que seu processo permanece no estado em cache e reduzir o número de inicializações a frio do app, você precisa liberar de forma agressiva todos os recursos que podem ser facilmente reconstruídos quando o usuário retoma a sessão.
Este exemplo de código mostra como implementar o callback onTrimMemory() para responder
a diferentes eventos relacionados à memória:
Kotlin
import android.content.ComponentCallbacks2
// Other import statements.
class MainActivity : AppCompatActivity(), ComponentCallbacks2 {
// Other activity code.
/**
* Release memory when the UI becomes hidden or when system resources become low.
* @param level the memory-related event that is raised.
*/
override fun onTrimMemory(level: Int) {
if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
// Release memory related to UI elements, such as bitmap caches.
}
if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
// Release memory related to background processing, such as by
// closing a database connection.
}
}
}
Java
import android.content.ComponentCallbacks2;
// Other import statements.
public class MainActivity extends AppCompatActivity
implements ComponentCallbacks2 {
// Other activity code.
/**
* Release memory when the UI becomes hidden or when system resources become low.
* @param level the memory-related event that is raised.
*/
public void onTrimMemory(int level) {
if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
// Release memory related to UI elements, such as bitmap caches.
}
if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
// Release memory related to background processing, such as by
// closing a database connection.
}
}
}
Conferir a quantidade de memória necessária
Para permitir vários processos em execução, o Android define um limite rígido para o tamanho de heap
atribuído a cada app. O limite exato varia entre dispositivos com base na quantidade de RAM
disponível. Se o app atingir a capacidade de heap e tentar alocar mais memória, o sistema vai gerar um OutOfMemoryError.
Para evitar a falta de memória, consulte o sistema e determine quanto espaço de heap há
disponível no dispositivo atual. Para fazer isso, chame getMemoryInfo(). Isso retorna um objeto
ActivityManager.MemoryInfo que fornece informações sobre o
status atual da memória do dispositivo, incluindo a quantidade disponível, total e
o limite de memória—o nível de memória em que o sistema começa a encerrar
processos. O objeto ActivityManager.MemoryInfo também expõe
lowMemory, um booleano simples que informa se a memória do dispositivo
está acabando.
O snippet de código de exemplo abaixo mostra como usar o método getMemoryInfo()
no seu app.
Kotlin
fun doSomethingMemoryIntensive() {
// Before doing something that requires a lot of memory,
// check whether the device is in a low memory state.
if (!getAvailableMemory().lowMemory) {
// Do memory intensive work.
}
}
// Get a MemoryInfo object for the device's current memory status.
private fun getAvailableMemory(): ActivityManager.MemoryInfo {
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
return ActivityManager.MemoryInfo().also { memoryInfo ->
activityManager.getMemoryInfo(memoryInfo)
}
}
Java
public void doSomethingMemoryIntensive() {
// Before doing something that requires a lot of memory,
// check whether the device is in a low memory state.
ActivityManager.MemoryInfo memoryInfo = getAvailableMemory();
if (!memoryInfo.lowMemory) {
// Do memory intensive work.
}
}
// Get a MemoryInfo object for the device's current memory status.
private ActivityManager.MemoryInfo getAvailableMemory() {
ActivityManager activityManager = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(memoryInfo);
return memoryInfo;
}
Monitorar falhas de pouca memória
As falhas por pouca memória (LMKs) visíveis ao usuário ocorrem quando a memória do sistema fica muito baixa. Quando a memória está baixa, o
lmkd (daemon
low memory killer) encerra processos com base no oom_adj_score deles. Os apps que estão
em cache ou executando um serviço sem uma interface associada (como um job) têm as
pontuações mais altas e são encerrados primeiro. Se a memória continuar muito baixa, o
daemon será forçado a recuperar memória de processos com um oom_adj_score de 0.
Como essa pontuação é reservada para apps visíveis, a encerramento deles resulta em uma
saída imediata e não normal do processo. Para o usuário final, parece que o app
falhou, muitas vezes ignorando os mecanismos padrão de salvamento de estado do ciclo de vida e
resultando na perda do progresso do usuário.
O encerramento de processos em primeiro plano é um foco principal no Android vitals porque eles servem como um proxy de alta fidelidade para o gerenciamento inadequado de memória. Embora qualquer taxa de LMK acima de 1% indique uma necessidade crítica de ação imediata, uma taxa baixa não é necessariamente um indicador de integridade. Uma taxa baixa de LMK percebida pelo usuário pode significar que o daemon LMK está encerrando processos com frequência em segundo plano, o que prejudica o desempenho de "inicialização com estado salvo" e a fluidez multitarefa. Portanto, recomendamos aderir às práticas recomendadas de memória, seja qual for sua pontuação atual de LMK, para garantir a estabilidade e a integridade do dispositivo a longo prazo.
Usar ProfilingManager para rastrear problemas de memória
A plataforma Android oferece
ProfilingManager, uma
API de observabilidade avançada que permite capturar dados do usuário em produção com base
em gatilhos definidos por você. Isso pode ajudar a identificar problemas de memória difíceis de reproduzir.
Dois novos acionadores introduzidos no Android 17 são especialmente úteis para identificar problemas de memória:
TRIGGER_TYPE_OOMindica que o app gerou umOutOfMemoryError. Ele é acionado na próxima vez que o app for iniciado após a falha, quando o app se registrar para acionadores de criação de perfil.- O
TRIGGER_TYPE_ANOMALYé acionado quando o sistema detecta um comportamento anômalo do app. Entre outras coisas, isso pode ser causado pelo uso da memória excessivo. Ele é acionado depois que o app apresenta uso da memória excessivo e antes que o sistema tome qualquer medida para interromper o processo ofensivo. Por exemplo, se o app exceder os limites de memória introduzidos no Android 17,TRIGGER_TYPE_ANOMALYserá acionado antes que o sistema encerre o app.
Para mais informações sobre como usar ProfilingManager para registrar e recuperar gatilhos de forma programática, consulte a documentação sobre criação de perfil com base em gatilhos.
Também é possível usar o perfil orientado por app para definir manualmente pontos de rastreamento de início e fim. Recomendamos fazer isso para capturar manualmente despejos de heap ou perfis de heap em áreas que você suspeita que possam ter vazamentos de memória ou uso da memória excessivo.
Usar mais construções de código com eficiência de memória
Alguns recursos do Android, classes Java e construções de código usam mais memória que outros. É possível minimizar a quantidade de memória que o app usa escolhendo alternativas mais eficientes no código.
Usar os serviços com moderação
É altamente recomendável não deixar os serviços em execução sem necessidade. Esse é um dos piores erros de gerenciamento de memória que um app Android pode cometer. Se o app precisa de um serviço para trabalhar em segundo plano, não o deixe em execução, a menos que ele precise realizar um job. Encerre o serviço quando ele concluir a tarefa. Caso contrário, você pode causar um vazamento de memória.
Quando você inicia um serviço, o sistema prefere manter o processo dele em execução. Esse comportamento torna os processos de serviços muito caros, porque a RAM usada por um serviço permanece indisponível para outros processos. Isso reduz o número de processos em cache que o sistema pode manter no cache de LRU, tornando a alternância de apps menos eficiente. Isso pode até gerar uma sobrecarga no sistema quando a memória está baixa, e o sistema não consegue manter processos suficientes para hospedar todos os serviços em execução.
Em geral, evite usar serviços persistentes, que criam demandas contínuas para a memória
disponível. Em vez disso, recomendamos que você use uma implementação alternativa, como WorkManager.
Para saber mais sobre como usar o WorkManager para programar processos em segundo plano, consulte Trabalho persistente.
Usar contêineres de dados otimizados
Algumas das classes fornecidas pela linguagem de programação não são otimizadas para
uso em dispositivos móveis. Por exemplo, a implementação
genérica HashMap pode ser bastante ineficiente
para a memória, já que precisa de um objeto de entrada diferente para cada mapeamento.
O framework do Android inclui vários contêineres de dados otimizados, como
SparseArray,
SparseBooleanArray e
LongSparseArray. Por exemplo, as classes SparseArray são mais eficientes porque evitam a necessidade de o sistema encaixotar automaticamente a chave e, às vezes, o valor, o que cria mais um ou dois objetos por entrada.
Se necessário, use matrizes brutas para ter uma estrutura de dados enxuta.
Cuidado com abstrações de código
Geralmente, desenvolvedores usam abstrações como uma boa prática de programação porque elas podem melhorar a flexibilidade e manutenção do código. No entanto, as abstrações geralmente exigem mais código para serem executadas. Conforme detalhado em Reduzir a pegada de código e recursos do app, uma base de código compilada maior aumenta diretamente a RAM física necessária para o app. Evite usar abstrações se elas não oferecerem um benefício significativo.
Usar protobufs leves para dados serializados
Os buffers de protocolo (protobufs) são um mecanismo extensível neutro em relação à linguagem e à plataforma que foi projetado pelo Google para serializar dados estruturados de forma semelhante ao XML, mas menor, mais rápido e mais simples. Se você usar protobufs para seus dados, use sempre protobufs leves no código do lado do cliente. Protobufs normais geram códigos extremamente detalhados, o que aumenta a pegada de código do app na RAM (consulte Gerenciar e otimizar a pegada de código do app) e contribui para o aumento do tamanho do APK.
Para mais informações, consulte o README do protobuf.
Tenha cuidado com vazamentos de memória
O gerenciamento inadequado de referências pode levar a vazamentos de memória em que os objetos sobrevivem aos períodos de vida úteis, impedindo que o coletor de lixo recupere a memória do objeto vazado. Para evitar vazamentos de memória, implemente um design que reconheça o ciclo de vida.
Para mais informações, consulte Vazamentos de memória.
Evitar rotatividade de memória
Os eventos de coleta de lixo não afetam a performance do app. No entanto, muitos eventos de coleta de lixo que ocorrem em um curto período podem descarregar a bateria rapidamente e aumentar marginalmente o tempo de configuração dos frames devido a interações necessárias entre o coletor de lixo e as linhas de execução do app. Quanto mais tempo o sistema passar na coleta de lixo, maior será o consumo da bateria.
Geralmente, a rotatividade de memória pode causar um grande número de eventos de coleta de lixo. Na prática, a rotatividade de memória descreve o número de objetos temporários alocados que ocorrem em determinado período.
Por exemplo, você pode alocar vários objetos temporários dentro de uma repetição de for.
Ou então, você pode criar novos objetos Paint ou
Bitmap dentro da função
onDraw()
de uma visualização. Em ambos os casos, o app cria muitos objetos de forma rápida e em volume elevado. Eles podem consumir rapidamente toda a memória disponível na geração jovem, forçando uma coleta de lixo.
Use o Memory Profiler para encontrar os locais no código em que a rotatividade de memória é alta e fazer as correções necessárias.
Depois de identificar as áreas problemáticas no seu código, reduza o número de alocações nas áreas com performance crítica. Mova itens para fora das repetições internas ou para uma estrutura de alocação baseada em fábrica.
Também é possível avaliar se pools de objetos seriam benéficos para seu caso de uso. Assim, em vez de jogar uma instância de objeto fora, você poderá liberá-la em um pool quando não for mais necessária. Na próxima vez que uma instância de objeto desse tipo for necessária, basta fazer o resgate desse pool em vez de alocar uma nova.
Avalie a performance para determinar se um pool de objetos é adequado em uma determinada situação. Há casos em que os pools de objetos podem piorar a performance. Embora evitem alocações, eles introduzem outras sobrecargas. Por exemplo, a manutenção do pool geralmente envolve sincronização, que tem uma sobrecarga significativa. Além disso, limpar a instância do objeto em um pool para evitar vazamentos de memória durante a liberação e inicializar esse objeto durante a transferência pode gerar uma sobrecarga maior que zero.
Armazenar mais instâncias de objetos do que necessário no pool também sobrecarrega o coletor de lixo. Embora os pools de objetos reduzam o número de invocações de coleta de lixo, eles acabam aumentando a quantidade de trabalho necessária para cada invocação, já que ela é proporcional ao número de bytes ativos (alcançáveis).