A memória de acesso aleatório (RAM) é um recurso
valioso em qualquer ambiente de desenvolvimento de software, mas
é 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 o 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 estáticas de membros, e
liberar quaisquer objetos Reference
no momento apropriado,
conforme definido pelos
callbacks do ciclo de vida.
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 a Visão geral do gerenciamento de memória do Android.
Monitorar a disponibilidade e o uso da memória
Antes de corrigir os problemas de uso da memória no seu app, é necessário encontrá-los. O Memory Profiler do Android Studio ajuda você a encontrar e diagnosticar problemas de memória porque permite:
- 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, do número de objetos Java alocados e de quando ocorre a coleta de lixo.
- Iniciar os eventos de coleta de lixo e gravar um snapshot do heap Java enquanto o app é executado.
- Registrar as alocações de memória do app e depois inspecionar todos os objetos alocados, conferir o stack trace para cada alocação e acessar o código correspondente no editor do Android Studio.
Liberar memória em resposta a eventos
Conforme descrito na Visão geral do gerenciamento de memória
no Android, o Android pode liberar a memória do app de várias maneiras ou encerrar totalmente o aplicativo,
se necessário, para liberar memória para tarefas essenciais. Para ajudar a equilibrar ainda mais a memória do sistema
e evitar a necessidade de encerrar o processo do app, implemente a interface ComponentCallbacks2
nas suas classes de Activity
. O
método de callback onTrimMemory()
fornecido
permite que seu app detecte eventos relacionados à memória independente do app estar em primeiro ou
segundo plano e, em seguida, libere objetos em resposta ao ciclo de vida do app ou aos eventos que indicam que o
sistema precisa liberar memória.
Por exemplo, você pode implementar o callback onTrimMemory()
para responder a diferentes eventos relacionados à memória, conforme mostrado abaixo:
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 was raised. */ override fun onTrimMemory(level: Int) { // Determine which lifecycle or system event was raised. when (level) { ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN -> { /* Release any UI objects that currently hold memory. The user interface has moved to the background. */ } ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE, ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW, ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> { /* Release any memory that your app doesn't need to run. The device is running low on memory while the app is running. The event raised indicates the severity of the memory-related event. If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system will begin killing background processes. */ } ComponentCallbacks2.TRIM_MEMORY_BACKGROUND, ComponentCallbacks2.TRIM_MEMORY_MODERATE, ComponentCallbacks2.TRIM_MEMORY_COMPLETE -> { /* Release as much memory as the process can. The app is on the LRU list and the system is running low on memory. The event raised indicates where the app sits within the LRU list. If the event is TRIM_MEMORY_COMPLETE, the process will be one of the first to be terminated. */ } else -> { /* Release any non-critical data structures. The app received an unrecognized memory level value from the system. Treat this as a generic low-memory message. */ } } } }
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 was raised. */ public void onTrimMemory(int level) { // Determine which lifecycle or system event was raised. switch (level) { case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN: /* Release any UI objects that currently hold memory. The user interface has moved to the background. */ break; case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE: case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW: case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL: /* Release any memory that your app doesn't need to run. The device is running low on memory while the app is running. The event raised indicates the severity of the memory-related event. If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system will begin killing background processes. */ break; case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND: case ComponentCallbacks2.TRIM_MEMORY_MODERATE: case ComponentCallbacks2.TRIM_MEMORY_COMPLETE: /* Release as much memory as the process can. The app is on the LRU list and the system is running low on memory. The event raised indicates where the app sits within the LRU list. If the event is TRIM_MEMORY_COMPLETE, the process will be one of the first to be terminated. */ break; default: /* Release any non-critical data structures. The app received an unrecognized memory level value from the system. Treat this as a generic low-memory message. */ break; } } }
O
callback onTrimMemory()
foi adicionado no Android 4.0 (nível 14 da API). Em versões anteriores,
use o
onLowMemory()
, que é aproximadamente equivalente ao evento
TRIM_MEMORY_COMPLETE
.
Verificar quanta memória você deve usar
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 para o tamanho de heap 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, você pode consultar o sistema para determinar
quanto espaço de heap há disponível no dispositivo atual.
É possível consultar o sistema para determinar esse número chamando
getMemoryInfo()
. Fazer isso retorna um objeto
ActivityManager.MemoryInfo
que fornece
informações sobre o status atual da
memória do dispositivo, incluindo o valor disponível, total e
do 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 um booleano
simples, lowMemory
,
que informa se a memória do dispositivo está acabando.
O snippet de código abaixo mostra um exemplo de como usar o método
getMemoryInfo()
no aplicativo.
Kotlin
fun doSomethingMemoryIntensive() { // Before doing something that requires a lot of memory, // check to see 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 to see 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 seu código.
Usar os serviços com moderação
Deixar um serviço em execução quando ele não é necessário é 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 mantenha em execução, a menos que ele precise realizar um job. Não esqueça de interromper o serviço quando ele tiver concluído a tarefa. Caso contrário, você pode causar um vazamento de memória por acidente.
Quando você inicia um serviço, o sistema prefere sempre manter o processo desse serviço 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 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 estiver baixa, e o sistema não vai conseguir manter processos suficientes para hospedar todos os serviços em execução.
Em geral, é preciso evitar o uso de serviços persistentes
devido às demandas contínuas que eles colocam na memória disponível. Em vez disso,
recomendamos que você use uma implementação alternativa,
como JobScheduler
. Para saber mais sobre
como usar o JobScheduler
para programar
processos em segundo plano, consulte
Otimizações em segundo plano.
Se você precisa usar um serviço, a
melhor maneira de limitar a vida útil dele é usando um IntentService
, que encerra
a si mesmo assim que o processamento da intent que o iniciou for concluído.
Para mais informações, leia
Executar um serviço em segundo plano.
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,
porque 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, o que cria mais um ou
dois objetos por entrada.
Se necessário, é possível usar matrizes brutas para ter uma estrutura de dados que seja realmente enxuta.
Cuidado com abstrações de código
Os desenvolvedores costumam usar abstrações simplesmente como uma boa prática de programação, porque as abstrações podem melhorar a flexibilidade e a manutenção do código. No entanto, elas têm um custo significativo: geralmente exigem que uma quantidade razoável de código a mais seja executado, que precisa de mais tempo e RAM para ser mapeado 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 são um mecanismo extensível que é neutro em relação à linguagem e à plataforma, projetado pelo Google para serializar dados estruturados, de forma semelhante ao XML, mas menor, mais rápido e mais simples. Se você decidir usar protobufs para seus dados, use sempre as opções mais leves no código do lado do cliente. Protobufs normais geram códigos extremamente detalhados, o que pode causar muitos tipos de problemas no app, como maior uso de RAM, aumento significativo no tamanho do APK e execução mais lenta.
Para mais informações, consulte a seção "Versão Lite" no readme do protobuf.
Evitar rotatividade de memória
Como mencionado anteriormente, 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 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 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.
Você precisa encontrar os locais no código em que a rotatividade de memória é alta antes de poder fazer correções. Para isso, use o Memory Profiler no Android Studio.
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 os mova para uma estrutura de alocação baseada em Fábrica (link em inglês).
Outra possibilidade é avaliar se os pools de objetos beneficiam o caso de uso. Com um pool de objetos, em vez de jogar uma instância de objeto fora, você a libera em um pool quando ela não é mais necessária. Na próxima vez que uma instância de objeto desse tipo for necessária, ela pode ser transferida do pool em vez de ser alocada.
Uma avaliação de performance completa é essencial 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 a 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 depois inicializar o objeto durante a transferência pode gerar uma sobrecarga maior que zero. Por fim, armazenar mais instâncias de objetos no pool do que o desejado também sobrecarrega o coletor de lixo (GC, na sigla em inglês). Embora os pools de objetos reduzam o número de invocações do GC, eles aumentam a quantidade de trabalho que precisa ser feita em cada invocação, já que é 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 muita memória sem que você saiba. O tamanho total do seu APK, incluindo bibliotecas de terceiros ou 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 seu código os componentes, recursos ou bibliotecas redundantes, desnecessários ou pesados.
Reduzir o tamanho geral do APK
É possível reduzir significativamente o uso da memória do seu app, reduzindo o tamanho geral dele. Tamanho de bitmap, 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 ajudar a reduzir o tamanho dos seus recursos e dependências externas. Essas ferramentas oferecem suporte a métodos modernos de redução de código, como a Compilação R8. O Android Studio 3.3 e versões anteriores usam o ProGuard em vez da Compilação R8.
Para saber mais sobre esse assunto, consulte o guia sobre como reduzir o tamanho do seu app.
Usar a Dagger 2 para injeção de dependência
Frameworks de injeção de dependência podem simplificar o código que você cria e fornecer um ambiente adaptável. Isso é ú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 o uso da Dagger 2 (link em inglês). A Dagger não usa reflexão para verificar o código do seu app. A implementação estática da Dagger durante a compilação significa que ela pode ser usada em apps Android sem custo de tempo de execução ou uso de memória desnecessários.
Outros frameworks de injeção de dependência que usam reflexão tendem a inicializar 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.
Cuidado ao usar bibliotecas externas
O código da biblioteca externa geralmente não é escrito para ambientes móveis e pode ser ineficiente quando usado para trabalhos em um cliente móvel. Quando você decide usar uma biblioteca externa, pode ser necessário otimizar essa biblioteca para dispositivos móveis. Planeje esse trabalho com antecedência e analise a biblioteca em termos de tamanho de código e área ocupada de RAM antes de decidir 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 micro protobufs, o que resulta 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.
O ProGuard pode
ajudar a remover APIs e recursos com as sinalizações corretas, mas não pode remover as
grandes dependências internas de uma biblioteca. Os recursos que você quer nessas
bibliotecas podem exigir dependências de nível inferior. Isso se torna especialmente
problemático quando você usa uma subclasse Activity
de uma
biblioteca, que costuma ter várias dependências,
quando as bibliotecas usam reflexão e assim por diante. O uso da reflexão é comum e significa que você precisa gastar
muito tempo ajustando manualmente o ProGuard para fazer a biblioteca funcionar.
Evite também usar uma biblioteca compartilhada para apenas um ou dois recursos dentre dezenas. Não é recomendável importar 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. Caso contrário, você pode criar uma implementação própria.