Gerenciar a memória em jogos de forma eficiente

Na plataforma Android, o sistema tenta usar o máximo de memória do sistema (RAM, na sigla em inglês) possível e executa várias otimizações de memória para liberar espaço quando necessário. Essas otimizações podem ter um efeito negativo no jogo, na lentidão ou à disposição total. Saiba mais sobre essas otimizações no tópico Alocação de memória entre processos.

Esta página explica as etapas que você pode seguir para evitar que condições de pouca memória afetem seu jogo.

Responda ao onTrimMemory()

O sistema usa onTrimMemory() para notificar o app de que a memória está acabando e o app pode ser encerrado. Muitas vezes, esse é o único aviso que seu app recebe. Esse callback tem alta latência em relação ao low-memory killer (LMK), por isso é essencial responder rapidamente ao callback.

Em resposta a esse callback, reduza a velocidade, o número e o tamanho das alocações. onTrimMemory() transmite uma constante que indica a gravidade, mas é necessário responder ao primeiro aviso, já que é possível alocar mais rapidamente do que o onTrimMemory() pode reagir.

Kotlin

class MainActivity : AppCompatActivity(), ComponentCallbacks2 {
    override fun onTrimMemory(level: Int) {
        when (level) {
            ComponentCallbacks2.TRIM_MEMORY_MODERATE,
                ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW,
                ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> // Respond to low memory condition
            else -> Unit
        }
    }
}

Java

public class MainActivity extends AppCompatActivity implements ComponentCallbacks2 {
    public void onTrimMemory(int level) {
        switch (level) {
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
              // Respond to low memory condition
                break;
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
              // Respond to low memory condition
                break;
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
              // Respond to low memory condition
                break;
            default:
                break;

C#

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

class LowMemoryTrigger : MonoBehaviour
{
    private void Start()
    {
        Application.lowMemory += OnLowMemory;
    }
    private void OnLowMemory()
    {
        // Respond to low memory condition (e.g., Resources.UnloadUnusedAssets())
    }
}

Usar a API Memory Advice Beta

A API Memory Advice foi desenvolvida como uma alternativa para onTrimMemory, que tem recall e precisão muito maiores na previsão de LMKs iminentes. Para fazer isso, a API estima a quantidade de recursos de memória que estão em uso e notifica o app quando determinados limites são excedidos. A API também pode informar a porcentagem estimada de uso de memória diretamente ao app. Você pode usar a API Memory Advice como uma alternativa aos eventos onTrimMemory para gerenciar memória.

Para usar a API Memory Advice, veja o Guia explicativo.

Conservar a capacidade da memória

Gerencie a memória de maneira conservadora para evitar a falta de memória. Veja alguns itens a serem considerados:

  • Tamanho da RAM física: os jogos geralmente usam entre ¼ e ½ do valor da RAM física no dispositivo.
  • Tamanho máximo da zRAM: mais zRAM significa que o jogo pode ter mais memória para alocar. Esse valor pode variar de acordo com o dispositivo. Procure SwapTotal em /proc/meminfo para encontrar esse valor.
  • Uso da memória do SO: os dispositivos que designam mais RAM para os processos do sistema deixam menos memória para o jogo. O sistema encerra o processo do jogo antes de encerrar processos do sistema.
  • Uso de memória dos apps instalados: teste seu jogo em dispositivos que têm muitos apps instalados. Os apps de redes sociais e chat precisam ser executados constantemente e afetar a quantidade de memória livre.

Se não for possível usar uma capacidade de memória conservadora, tente uma abordagem mais flexível. Se o sistema tiver problemas de pouca memória, reduza a quantidade de memória que o jogo está usando. Por exemplo, aloque texturas de baixa resolução ou armazene menos sombreadores em resposta a onTrimMemory(). Essa abordagem dinâmica para a alocação de memória requer mais trabalho do desenvolvedor, especialmente na fase de design do jogo.

Evite a sobrecarga

A sobrecarga ocorre quando a memória livre é baixa, mas não o suficiente para encerrar o jogo. Nessa situação, kswapd recuperou páginas de que o jogo ainda precisa, por isso, tenta recarregar as páginas da memória. Não há espaço suficiente, então as páginas continuam sendo trocadas (troca contínua). O rastreamento do sistema informa essa situação como uma linha de execução em que kswapd é executado continuamente.

Um sintoma de sobrecarga é um tempo longo para a renderização do frame, possivelmente um segundo ou mais. Reduza o consumo de memória do jogo para resolver essa situação.

Use as ferramentas disponíveis

O Android tem uma coleção de ferramentas que ajudam a entender como o sistema gerencia a memória.

Meminfo

Essa ferramenta coleta estatísticas sobre a memória para mostrar quanta memória PSS foi alocada e as categorias para as quais ela foi usada.

Veja as estatísticas do meminfo de uma das seguintes maneiras:

  • Use o comando adb shell dumpsys meminfo package-name;
  • Use a chamada MemoryInfo da API Android Debug.

A estatística PrivateDirty mostra a quantidade de RAM dentro do processo que não pode ser paginada no disco e não é compartilhada com outros processos. A maior parte desse valor fica disponível para o sistema quando esse processo é encerrado.

Tracepoints de memória

Os tracepoints de memória rastreiam a quantidade de memória RSS que seu jogo está usando. Calcular o uso de memória RSS é muito mais rápido do que calcular o uso do PSS. Como é mais rápido de calcular, o RSS mostra uma granularidade mais refinada nas alterações no tamanho da memória para medições mais precisas do uso de memória de pico. Portanto, é mais fácil observar picos que possam fazer com que o jogo fique sem memória.

Perfetto e rastreamentos longos

O Perfetto (link em inglês) é um pacote de ferramentas para coletar informações de desempenho e memória de um dispositivo e exibi-lo em uma IU baseada na Web. É compatível com rastreamentos arbitrariamente longos para que você possa visualizar como o RSS muda ao longo do tempo. Também é possível emitir consultas SQL dos dados produzidos para processamento off-line. Ative os rastreamentos longos no app Rastreamento do sistema. Verifique se a categoria memory:Memory está ativada para o rastreamento.

Heapprofd

heapprofd é uma ferramenta de rastreamento de memória que faz parte do Perfetto. Essa ferramenta pode ajudar a encontrar vazamentos de memória ao mostrar onde a memória foi alocada usando malloc. O heapprofd pode ser iniciado usando um script Python. Como a ferramenta tem baixa sobrecarga, isso não afeta o desempenho como outras ferramentas, como a Malloc Debug.

Relatório de bugs

bugreport é uma ferramenta de geração de registros para descobrir se o jogo falhou porque ficou sem memória. A saída da ferramenta é muito mais detalhada do que a do logcat. Ele é útil para depuração de memória porque mostra se o jogo falhou por falta de memória ou se foi encerrado pelo LMK.

Para mais informações, consulte Capturar e ler relatórios de bugs.