Esta página ensina a reduzir proativamente o uso da memória no seu app. Para saber mais sobre como o sistema operacional Android gerencia a memória, consulte a 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 o momento e o local em que 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.
Monitorar a disponibilidade e o uso da 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. Confira o que ele permite fazer:
- 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 o app está usando, do número de objetos Java alocados e de quando ocorre a coleta de lixo.
- Iniciar os eventos de coleta de lixo e fazer uma captura de tela do heap Java enquanto o app é executado.
- Registrar as alocações de memória do app, inspecionar todos os objetos alocados, conferir cada alocação no stack trace e acessar o código correspondente no editor do Android Studio.
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
onTrimMemory()
método de callback notifica seu app sobre eventos relacionados ao ciclo de vida ou à memória que apresentam uma boa
é uma oportunidade para o app reduzir voluntariamente o uso de memória. Liberar memória pode reduzir a
probabilidade de seu aplicativo ser encerrado pelo
low-memory killer.
Implemente o callback onTrimMemory()
para responder a diferentes eventos relacionados à
memória, conforme mostrado neste exemplo:
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 do 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()
.
O sistema retornará um
objeto
ActivityManager.MemoryInfo
com informações sobre o status atual da memória do dispositivo, incluindo a quantidade
disponível, o total e o limite, ou seja, 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; }
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 que um serviço atue 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 aumenta muito o peso dos processos de serviços, já que a RAM usada por um serviço permanece indisponível para outros processos. Isso reduz o número de processos que o sistema pode manter no cache de LRU, tornando a troca de apps menos eficiente. Isso pode até gerar uma sobrecarga no sistema quando a memória estiver baixa, e o sistema não conseguirá manter processos suficientes para hospedar todos os serviços em execução.
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 estã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
encaixotar automaticamente
a chave e, às vezes, o valor, criando 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, abstrações são significativamente mais pesadas, já que costumam exigir mais código que precisa ser executado, o que demanda mais tempo e RAM para mapear o código na memória. Evite usar abstrações se elas não oferecerem um benefício significativo.
Usar protobufs leves para dados serializados
Os buffers de protocolo (protobufs) (link em inglês) 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 pode causar muitos problemas no app, como maior uso de RAM, aumento significativo no tamanho do APK e execução mais lenta.
Para saber mais, consulte o README do protobuf.
Evitar rotatividade de memória
Os eventos de coleta de lixo não afetam a performance do app. No entanto, muitos deles que ocorrem em um curto período podem descarregar a bateria rapidamente e aumentar marginalmente o tempo de exibição dos frames devido a interações necessárias entre o coletor de lixo e as linhas de execução do aplicativo. 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 mais jovem, forçando uma
coleta de lixo.
Use o Memory Profiler para encontrar as áreas do código em que a rotatividade de memória é alta e fazer as correções necessárias.
Depois de identificar os problemas 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 (link em inglês).
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 uma 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 aumentam a quantidade de trabalho que precisa ser feita em cada invocação, já que ela é proporcional ao número de bytes ativos (alcançáveis).
Remover recursos e bibliotecas que consomem muita memória
Alguns recursos e bibliotecas do código podem consumir memória sem que você perceba. O tamanho total do seu APK, incluindo bibliotecas de terceiros e recursos incorporados, pode afetar a quantidade de memória que o app consome. Você pode melhorar o consumo de memória do seu app removendo do código os componentes, bibliotecas e recursos redundantes, desnecessários ou pesados.
Reduzir o tamanho geral do APK
Você pode diminuir significativamente o uso da memória do seu app reduzindo o tamanho geral dele. O tamanho de bitmaps, recursos, frames de animação e bibliotecas de terceiros podem aumentar o tamanho do app. O Android Studio e o Android SDK oferecem várias ferramentas para reduzir o tamanho dos seus recursos e das dependências externas. Essas ferramentas têm suporte a métodos modernos de redução de código, como a Compilação R8.
Para saber mais sobre como reduzir o tamanho geral do app, consulte Reduzir o tamanho do app.
Usar Hilt ou Dagger 2 para injeção de dependência
Frameworks de injeção de dependência podem simplificar o código programado 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. A Hilt é uma biblioteca de injeção de dependência para Android executada com base no framework 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 execução ou uso de 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 um valor mais significativo de ciclos de CPU e RAM, bem como causar um atraso considerável na inicialização do app.
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.
Com as flags corretas, o ProGuard pode remover APIs e recursos,
mas não pode remover grandes dependências internas de uma biblioteca. Os recursos necessários
nessas bibliotecas podem exigir dependências de nível inferior. Isso se torna particularmente problemático quando você
usa uma subclasse Activity
de uma
biblioteca, que costuma ter várias dependências, quando as bibliotecas usam reflexão, o que
é comum e significa que você precisa ajustar o ProGuard manualmente para fazer a biblioteca funcionar.
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ê quiser usar uma biblioteca, procure uma implementação que corresponda às suas necessidades. Você também pode criar uma implementação própria.