Armazenamento em cache de apps

O Android 11 (nível 30 da API) e versões mais recentes oferecem suporte ao freezer de apps armazenados em cache. Esse recurso interrompe a execução de processos armazenados em cache e reduz o uso de recursos por apps com comportamento inadequado que podem tentar operar enquanto estão armazenados em cache.

O freezer de apps armazenados em cache mantém os apps na RAM, mas fora da CPU. Se o Android determinar que um app não deve estar funcionando, mas pode ser necessário no futuro, ele congela o processo do app em vez de encerrá-lo. Isso evita uma inicialização a frio quando o app for necessário novamente.

O Android congela apps armazenados em cache migrando os processos deles para um cgroup congelado. Isso reduz o consumo ativo e inativo da CPU na presença de apps armazenados em cache ativos. É possível ativar o freezer de apps usando uma flag de configuração do sistema ou uma opção do desenvolvedor.

No Android 14 (nível 34 da API) e versões mais recentes, o freezer de apps armazenados em cache inclui os seguintes comportamentos robustos:

  • Os processos de apps no estado armazenado em cache são congelados 10 segundos após entrarem nesse estado.
  • O sistema descongela imediatamente um processo de app congelado durante um evento de ciclo de vida. Esses eventos incluem o recebimento de uma intent, a inicialização de um serviço de job ou o usuário retomando uma atividade.

O ActivityManagerService gerencia todos os processos de apps e toma decisões sobre o ciclo de vida deles. O CachedAppOptimizer é responsável por congelar o processo do app.

Quando um processo de app é congelado, todas as linhas de execução são suspensas e não podem realizar o trabalho da CPU até serem descongeladas. Como resultado, o app não pode realizar a coleta de lixo (GC, na sigla em inglês) e não pode responder a eventos de redução de memória. Para mais detalhes, consulte ComponentCallbacks2.onTrimMemory(int). Para acomodar isso, a partir do Android 14:

  • Os apps com uma instância Activity visível são notificados sobre TRIM_MEMORY_UI_HIDDEN assim que passam para o segundo plano. Os apps que permanecem em um ciclo de vida sem uma interface, como apps com um serviço em primeiro plano, podem receber TRIM_MEMORY_BACKGROUND. Outros eventos de redução não são entregues porque, quando os apps são qualificados para esses eventos, eles precisam ser congelados.
  • Logo após entrar no estado armazenado em cache, o sistema pode solicitar que o ambiente de execução do app realize uma GC em preparação para um possível congelamento.
  • Quando um processo de app é congelado, outras etapas de compactação de memória podem ocorrer, como gravar páginas sujas no armazenamento de backup e trocar páginas anônimas para ZRAM.
  • Se todos os processos de um app específico forem congelados, o sistema vai encerrar todos os soquetes TCP ativos mantidos pelo app. Isso impede que o lado do servidor do soquete envie pings de keepalive TCP que ativariam o modem do dispositivo.

Os processos de apps armazenados em cache são descongelados quando o estado do processo é elevado de armazenado em cache para um estado de maior importância. Para reduzir eventos de descongelamento no Android 14 e versões mais recentes, o sistema enfileira transmissões registradas em contexto enquanto o app está no estado armazenado em cache. As transmissões registradas em contexto são receptores que um app registra dinamicamente chamando Context.registerReceiver. O sistema entrega essas transmissões enfileiradas somente depois que o app é descongelado. Por outro lado, o sistema não enfileira transmissões declaradas pelo manifesto. As transmissões declaradas pelo manifesto são receptores declarados estaticamente em AndroidManifest.xml usando o elemento <receiver>. O sistema descongela imediatamente o app armazenado em cache para entregar transmissões declaradas pelo manifesto.

Impacto na integridade do sistema

O Android encerra o processo de app armazenado em cache usado há menos tempo se houver mais de MAX_CACHED_PROCESSES processos de apps armazenados em cache. Em dispositivos compatíveis com o Android 14 ou versões mais recentes, MAX_CACHED_PROCESSES é significativamente aumentado, permitindo que os dispositivos mantenham muito mais processos de apps armazenados em cache na RAM.

Manter mais apps armazenados em cache na RAM gera uma redução de até 30% nas inicializações a frio, com reduções de escalonamento com base na RAM total do dispositivo. Ao mesmo tempo, o consumo de CPU por apps armazenados em cache é minimizado, levando a uma economia significativa de bateria.

Isenções de freezer

Em determinadas condições, um processo de app pode entrar no estado armazenado em cache, mas permanecer descongelado. Essas isenções são detalhes de implementação e podem mudar em versões futuras do Android:

  • Bloqueios de arquivo:se um processo armazenado em cache tiver um bloqueio de arquivo que bloqueia outros processos não armazenados em cache, o processo que mantém o bloqueio não será congelado.
  • BIND_WAIVE_PRIORITY vinculações: os processos de apps com vinculações recebidas criadas usando Context.BIND_WAIVE_PRIORITY podem entrar no estado armazenado em cache, mas permanecer descongelados até que todos os processos de cliente conectados também sejam armazenados em cache. Essa isenção oferece suporte a apps de vários processos, como navegadores da Web que usam guias personalizadas.

Implementar o freezer de apps

O freezer de apps armazenados em cache aproveita o freezer cgroup v2 do kernel. Os dispositivos enviados com um kernel compatível podem ativá-lo. Ative a opção do desenvolvedor Suspender execução para apps armazenados em cache ou defina a flag de configuração do dispositivo activity_manager_native_boot use_freezer como true. Exemplo:

adb shell device_config put activity_manager_native_boot use_freezer true && adb reboot

O freezer é desativado quando você define a flag use_freezer como false ou desativa a opção do desenvolvedor. Exemplo:

adb shell device_config put activity_manager_native_boot use_freezer false && adb reboot

É possível alternar essa configuração mudando uma configuração do dispositivo em uma versão ou atualização de software.

Para substituir MAX_CACHED_PROCESSES, por exemplo, para definir o valor como 1024 para teste:

adb shell device_config put activity_manager max_cached_processes 1024
adb shell device_config set_sync_disabled_for_tests persistent

Para reverter a substituição de MAX_CACHED_PROCESSES:

adb shell device_config delete activity_manager max_cached_processes
adb shell device_config set_sync_disabled_for_tests none

O freezer de apps não expõe APIs oficiais e não tem um cliente de implementação de referência, mas usa as APIs do sistema ocultas setProcessFrozen para congelar um processo individual e enableFreezer para ativar ou desativar o congelamento globalmente.

Processar recursos personalizados

Não é esperado que os processos de apps façam qualquer trabalho quando armazenados em cache, mas alguns apps podem ter recursos personalizados com suporte a processos que precisam ser executados enquanto estão armazenados em cache. Quando o freezer de apps está ativado em um dispositivo que executa esses apps, os processos armazenados em cache são congelados e podem impedir que os recursos personalizados funcionem.

Como solução alternativa, você pode mudar o status do processo para não armazenado em cache antes que ele precise fazer qualquer trabalho. Essa mudança permite que os apps permaneçam ativos. Exemplos de status ativos incluem um serviço em primeiro plano vinculado ou status de primeiro plano.

Modos de falha comuns

Quando os processos de apps são congelados, a comunicação entre processos (IPC, na sigla em inglês) ou o agendamento de tarefas inadequados podem levar a encerramentos de apps ou comportamento inesperado.

Transações de binder síncronas para processos congelados

Quando um processo de app cliente envia uma transação de binder síncrona para um processo de app servidor congelado, o sistema encerra imediatamente o processo de app servidor. Isso impede que a linha de execução do cliente seja bloqueada indefinidamente enquanto aguarda uma resposta do servidor congelado. A linha de execução do cliente recebe RemoteException, e todos os listeners registrados são acionados. Para mais detalhes, consulte IBinder.linkToDeath.

Causa raiz:essa falha geralmente é causada por um bug no app cliente. Quando um cliente se vincula a um serviço, o processo do servidor é vinculado ao cliente e é impedido de entrar no estado armazenado em cache antes do cliente. Para mais detalhes, consulte Context.bindService. No entanto, depois que o cliente chama Context.unbindService, o processo do servidor pode ser armazenado em cache e congelado. Se o cliente continuar usando a referência IBinder armazenada em cache após a desvinculação, ele corre o risco de se comunicar com um processo congelado.

Para evitar esse problema, verifique se os apps clientes descartam IBinder referências imediatamente após chamar Context.unbindService.

Estouro do buffer de transação de binder assíncrono

Quando um processo de app servidor recebe transações de binder assíncronas (oneway) enquanto está congelado, as transações são armazenadas em buffer em um buffer por processo. Se o servidor receber muitas transações assíncronas enquanto estiver congelado, o buffer vai estourar e o sistema vai encerrar o processo de app servidor.

Para evitar esse estouro de buffer, evite enviar transações de binder assíncronas excessivas para processos que possam ser armazenados em cache ou congelados.

Execução repetida de tarefas agendadas após o descongelamento

Se um app executar tarefas repetitivas, elas serão suspensas enquanto o processo estiver congelado. Para mais detalhes, consulte ScheduledThreadPoolExecutor.scheduleAtFixedRate ou Timer.scheduleAtFixedRate. Quando o processo é descongelado, as execuções perdidas acumuladas podem ser executadas rapidamente em sequência, sem praticamente nenhum atraso.

Para evitar um aumento de execuções quando o app é descongelado, use scheduleWithFixedDelay em vez de scheduleAtFixedRate para tarefas em segundo plano. Também é possível usar WorkManager.

Testar e solucionar problemas do freezer de apps

Para verificar se o freezer de apps está funcionando conforme o esperado ou para solucionar problemas relacionados ao freezer, use as seguintes ferramentas e comandos de diagnóstico:

Comandos do gerenciador de atividades

É possível usar comandos adb shell am para controlar manualmente o congelamento e a compactação de um processo específico:

  • Forçar um processo a congelar:

    adb shell am freeze <process>
  • Forçar um processo a descongelar:

    adb shell am unfreeze <process>
  • Forçar uma compactação de memória completa em um processo:

    adb shell am compact full <process>

Exame do Logcat

Visualize o Logcat para ver as entradas congeladas e descongeladas sempre que um processo migrar para dentro ou para fora do freezer:

adb logcat | grep -i "\(freezing\|froze\)"

Os registros de motivo de descongelamento geram valores enumerados da enumeração de buffer de protocolo UnfreezeReason.

Inspeção do Dumpsys

Verifique uma lista de processos congelados usando dumpsys activity:

adb shell dumpsys activity | grep -A 20 "Apps frozen:"

Verifique a presença do arquivo /sys/fs/cgroup/uid_0/cgroup.freeze.

ApplicationExitInfo

Para consultar o motivo de um encerramento de processo anterior, consulte ActivityManager.getHistoricalProcessExitReasons. Se um processo de app foi encerrado devido a um problema relacionado ao freezer, como receber uma transação de binder síncrona enquanto estava congelado, o motivo de saída será definido como ApplicationExitInfo.REASON_FREEZER.

Rastreamento do Perfetto

Os eventos relacionados ao freezer são emitidos para uma faixa chamada Freezer no processo system_server em rastreamentos do Perfetto:

  • As fatias Freeze e Unfreeze indicam quando um processo muda de estado.
  • Os eventos updateAppFreezeStateLSP mostram quando o servidor do sistema reexamina os atributos do processo para tomar decisões de congelamento ou descongelamento.

É possível inspecionar esses eventos diretamente na interface do Perfetto ou analisá-los usando o PerfettoSQL:

INCLUDE PERFETTO MODULE slices.with_context;
SELECT *
FROM process_slice
WHERE process_name = "system_server"
AND track_name = "Freezer"
AND (name LIKE "Freeze %" OR name LIKE "Unfreeze %");

Na biblioteca padrão do PerfettoSQL, os eventos do freezer também são resumidos na tabela android_freezer_events.