Muitos apps realizam tarefas legítimas quando a IU não está mais visível, como
fazer o download de grandes pacotes de recursos, sincronizar bancos de dados locais, transmitir áudio,
registrar exercícios físicos ou manter uma sessão de dispositivo conectado. Quando
esse trabalho em segundo plano é executado no mesmo processo do SO que a IU em primeiro plano e
o mecanismo gráfico, todo o processo permanece em um nível de importância elevado
(oom_score_adj entre 0 e 250), junto com toda a memória alocada
pela IU.
Esta página explica por que isso prejudica seu app e o restante do
dispositivo, como mover o trabalho em segundo plano para um processo
leve e dedicado usando android:process e como verificar a
economia de memória usando adb shell e o Perfetto.
Por que o trabalho de processo único retém a memória da interface
O Android gerencia a memória e o encerramento de processos na granularidade de um processo
do SO (pid), não de componentes individuais (Activity, Service ou
BroadcastReceiver). O Activity Manager (OomAdjuster) atribui a cada processo
um único valor de oom_score_adj que reflete o componente ativo mais importante
ou a vinculação de entrada nesse processo:
- Quando o usuário interage com o
Activity, o processo é executado no estadoTOP(oom_score_adj = 0). - Quando o usuário sai do app e não há componentes ativos em segundo plano, o processo é movido para o bucket
PREV(oom_score_adj = 700) ouCACHED(oom_score_adj >= 900). No bucket em cache, a plataforma congela o processo com o freezer do cgroup v2, compacta a memória comCachedAppOptimizere permite que olmkdencerre o processo primeiro sob pressão de memória. - No entanto, se o mesmo processo também executar um serviço em primeiro plano (como
os tipos
dataSync,mediaPlayback,connectedDeviceoulocation, emoom_score_adj = 200) ou hospedar um serviço ao qual o sistema ou outro cliente importante se vincula, todo o processo vai permanecer com essa prioridade elevada mesmo depois que oActivityfor interrompido.
Quando um processo de interface pesado permanece em oom_score_adj = 200, várias categorias grandes
de memória permanecem residentes:
Activityinterrompida e hierarquia de visualização: a menos que aActivityseja explicitamente concluída, aWindow, a árvore de visualização, as estruturas de layout e o estado de visualização permanecem acessíveis no heap do Java enquanto o usuário está em outro app.- Contextos gráficos nativos e alocações de GPU: o
HardwareRendererpadrão mantém o contexto e os caches da GPU, enquanto umActivityinterrompido reside em um processo que não é armazenado em cache. Os apps e mecanismos de jogo que criam os próprios contextos do Vulkan (VkDevice,VkSwapchainKHR, pools de descritores, caches de pipeline) ou do OpenGL ES (EGLContext, texturas, buffers de vértice) também mantêm as estruturas de driver, os programas de sombreador e os buffers gráficos, a menos que o mecanismo desative explicitamente o dispositivo gráfico quando interrompido. - Fragmentação de heap e sobrecarga de ambiente de execução: mesmo quando um app libera
caches em
onTrimMemory, páginas de alocador nativo parcialmente preenchidas, páginas de cópia em gravação Zygote sujas, bibliotecas compartilhadas carregadas (.so) e metadados de ambiente de execução permanecem residentes na memória privada do processo. Como o processo está acima do limite de armazenamento em cache (oom_score_adj < 900), a plataforma não o congela nem executa a compactação de apps armazenados em cache. Para conferir mais de perto esse efeito com serviços vinculados, consulte Como colocar um serviço vinculado com uma interface que consome muita memória.
Como um processo em segundo plano otimizado protege seu app
Manter a memória do tamanho do primeiro plano em um estado de segundo plano coloca seu próprio trabalho em segundo plano em risco:
- Os limites de memória em segundo plano são mais rígidos do que os limites em primeiro plano. No Android 17 e versões mais recentes, o Limitador de memória aplica um limite de memória não visível menor ao app quando nenhum dos processos mostra uma interface visível, inclusive durante a execução de um serviço em primeiro plano. O limite abrange todos os processos do app juntos. Se o processo que executa o serviço em primeiro plano ainda mantiver a memória de interface e a memória gráfica, essa memória será contabilizada no limite menor. Quando o app excede o limite, o kernel o limita enquanto recupera as páginas, o que desacelera o download, a sincronização ou a reprodução em execução. Se o app continuar alocando depois que a troca for esgotada, o sistema vai encerrá-lo. Para verificar se o app foi afetado, consulte Limites de memória do app.
- Os processos maiores são encerrados primeiro quando há pressão de memória. Quando
o
lmkdprecisa liberar memória e está configurado para escolher o processo mais pesado em um bucket de importância (ro.lmk.kill_heaviest_task), um processo de serviço em primeiro plano que também contém centenas de MB de memória de interface é encerrado antes de processos menores com a mesma importância, e o trabalho em segundo plano é interrompido com ele. Consulte Interação entre kswapd e lmkd. - O estado da interface em cache pode ser congelado, compactado e gravado de volta na memória flash. Permitir que o estado da interface em primeiro plano seja movido para um processo em cache permite que o SO congele o processo, compacte a memória na zRAM e grave as páginas frias de volta no armazenamento flash. O consumo de memória residente diminui enquanto o estado da interface permanece intacto, dando ao trabalho em segundo plano e à interface em cache a melhor chance de sobreviver até que o usuário retorne.
- Os orçamentos de memória por processo se tornam práticos. Um processo que executa apenas trabalho em segundo plano tem um conjunto de trabalho pequeno e previsível. Portanto, é possível fornecer um orçamento de memória restrito sem limitar a interface.
A execução de um segundo processo pode parecer que aumenta o consumo total de memória do app, porque um segundo ambiente de execução tem um pequeno custo de referência. Essa comparação só é válida se um único processo puder liberar completamente todo o estado da IU e dos gráficos sempre que fizer a transição para o trabalho em segundo plano, o que os apps e os kits de ferramentas de IU não podem fazer de forma confiável na prática. Permitir que o SO congele, compacte e grave de volta um processo de interface em cache separado usa muito menos memória física no caso comum, mantém o app dentro do limite não visível e reduz as finalizações por falta de memória para o app e em todo o dispositivo.
Isolar o trabalho em segundo plano em um processo dedicado
Para eliminar a residência de IU e gráficos durante o trabalho em segundo plano, execute componentes de segundo plano de longa duração em um processo leve dedicado, mantendo a IU e o mecanismo de gráficos no processo principal:
| Processo | Declaração do manifesto | Componentes hospedados | Estado quando o usuário sai da interface durante o trabalho em segundo plano |
|---|---|---|---|
| Processo principal da interface | Padrão (com.example.app) |
Activity, visualizações de interface, caches de imagem, renderizador Vulkan ou OpenGL ES, mecanismo de áudio e cena
|
Cai para PREV e depois para CACHED
(oom_score_adj >= 900). Congelado, compactado e gravado
de volta no flash.
|
| Processo de worker em segundo plano | android:process=":bg" |
Service em primeiro plano, downloader de recursos em segundo plano, worker de sincronização
ou gerenciador de dispositivos conectados
|
É executado em FGS (oom_score_adj = 200) apenas com
o heap e o código do worker, sem visualizações de interface ou contextos de GPU.
|
Declarar os orçamentos de processo e memória
Atribua o plano de fundo Service a um subprocesso particular definindo
android:process=":bg" em AndroidManifest.xml. Os dois-pontos iniciais (:)
criam um processo particular com escopo para o UID do app.
No Android 17 QPR2 (nível 37.2 da API) e versões mais recentes, combine a divisão de processos com
orçamentos de memória do app por processo dentro do
bloco <processes> para que o sistema operacional aplique um limite rígido de conjunto de trabalho
ao worker em segundo plano sem restringir o processo de interface
em primeiro plano:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:name=".HeavyApplication"
android:label="@string/app_name">
<!-- App-wide budgets that cover every process, including :bg -->
<memory-budget android:maxMb="512" />
<memory-budget
android:maxMb="128"
android:state="background" />
<processes>
<!-- Tight budget for the headless background worker process -->
<process android:process=":bg">
<memory-budget android:maxMb="32" />
</process>
</processes>
<activity
android:name=".MainActivity"
android:exported="true" />
<service
android:name=".AssetDownloadService"
android:process=":bg"
android:foregroundServiceType="dataSync"
android:exported="false" />
</application>
</manifest>
O uso de memória em :bg é contabilizado no próprio orçamento e no orçamento de todo o app. Em dispositivos com versões anteriores ao Android 17 QPR2, o sistema
ignora <memory-budget>, enquanto android:process=":bg" funciona em todos os níveis
da API.
Inicialização de portão em Application.onCreate
O Android instancia sua classe Application e chama Application.onCreate
em todos os processos gerados para seu pacote. Uma armadilha comum ao adotar o
android:process=":bg" é deixar kits de ferramentas de interface pesados, pools de carregamento de imagens,
SDKs do Analytics ou inicialização de mecanismo nativo (System.loadLibrary) dentro de um
método Application.onCreate desprotegido. Isso infla o processo :bg
com a mesma pegada de referência que você está tentando evitar.
Verifique Application.getProcessName, que está disponível
no Android 9 (nível 28 da API) e versões mais recentes, em onCreate e inicialize apenas
as dependências exigidas pelo processo atual:
Kotlin
class HeavyApplication : Application() {
override fun onCreate() {
super.onCreate()
val processName = Application.getProcessName()
if (processName != null && processName.endsWith(BG_PROCESS_SUFFIX)) {
initBackgroundWorkerProcess()
return
}
initMainUiProcess()
}
private fun initBackgroundWorkerProcess() {
// Initialize only lightweight HTTP, storage, and sync primitives.
// Avoid loading native rendering libraries or allocating UI caches.
}
private fun initMainUiProcess() {
// Initialize UI frameworks, image caches, and native engines.
System.loadLibrary("heavy_engine_jni")
}
companion object {
private const val BG_PROCESS_SUFFIX = ":bg"
}
}
Java
public final class HeavyApplication extends Application {
private static final String BG_PROCESS_SUFFIX = ":bg";
@Override
public void onCreate() {
super.onCreate();
String processName = Application.getProcessName();
if (processName != null && processName.endsWith(BG_PROCESS_SUFFIX)) {
initBackgroundWorkerProcess();
return;
}
initMainUiProcess();
}
private void initBackgroundWorkerProcess() {
// Initialize only lightweight HTTP, storage, and sync primitives.
// Avoid loading native rendering libraries or allocating UI caches.
}
private void initMainUiProcess() {
// Initialize UI frameworks, image caches, and native engines.
System.loadLibrary("heavy_engine_jni");
}
}
As bibliotecas que são inicializadas por um provedor de conteúdo, como a App Startup do Jetpack, são inicializadas apenas no processo principal, porque um provedor de conteúdo declarado sem android:process é executado no processo principal. Se o código em :bg
depender de uma biblioteca desse tipo, inicialize-a explicitamente em
initBackgroundWorkerProcess.
Se você criar com um mecanismo de jogo, como o Unity, implemente o serviço em segundo plano
em uma biblioteca ou um plug-in simples do Android escrito em Java ou Kotlin que não
faça referência a classes de mecanismo, como UnityPlayer. O processo :bg nunca
carrega as bibliotecas nativas do mecanismo. Para confirmar, verifique se
adb shell cat /proc/<pid>/maps para o processo :bg não lista
libunity.so ou libil2cpp.so.
Comunicar sem fixar o processo da interface
Quando o usuário tem MainActivity aberto enquanto AssetDownloadService é executado em
:bg, a interface geralmente precisa mostrar o progresso do download em tempo real. A importância do processo
flui de um cliente para o processo que hospeda o serviço ou provedor de conteúdo
que ele usa. Portanto, a direção de cada dependência é importante:
- A vinculação da IU ao worker é segura. Quando
MainActivityse vincula aAssetDownloadService, o sistema eleva:bgà importância da IU visível. Quando o usuário sai, o processo da interface é armazenado em cache, e a vinculação não eleva mais nada. - O worker que depende do processo de interface não é seguro. Se o código em
:bgse vincular a um serviço ou consultar um provedor de conteúdo que é executado no processo principal, o sistema vai elevar o processo principal à importância elevada do worker, e a memória da interface vai ficar fixada novamente. Em vez disso, faça com que a interface registre um callback com o worker, como umMessengerou uma interface de callback da AIDL. Transmitir um objeto binder como um callback não muda a importância de nenhum dos processos.
Cancele o registro do callback antes de desvincular para que o worker nunca chame o processo da interface depois que o sistema o congelar. O exemplo a seguir se vincula ao worker apenas enquanto a IU está visível:
Kotlin
class MainActivity : Activity() {
private var downloadService: IBinder? = null
private val downloadConnection = object : ServiceConnection {
override fun onServiceConnected(
name: ComponentName,
service: IBinder
) {
downloadService = service
// Register a Messenger or AIDL callback for progress updates.
}
override fun onServiceDisconnected(name: ComponentName) {
// The binding stays active and reconnects if the worker restarts.
downloadService = null
}
}
override fun onStart() {
super.onStart()
val intent = Intent(this, AssetDownloadService::class.java)
bindService(intent, downloadConnection, Context.BIND_AUTO_CREATE)
}
override fun onStop() {
super.onStop()
if (downloadService != null) {
// Unregister the progress callback here, before unbinding.
downloadService = null
}
// Unbind the ServiceConnection even if onServiceConnected hasn't run
// yet.
unbindService(downloadConnection)
}
}
Java
public final class MainActivity extends Activity {
private IBinder downloadService;
private final ServiceConnection downloadConnection =
new ServiceConnection() {
@Override
public void onServiceConnected(
ComponentName name, IBinder service) {
downloadService = service;
// Register a Messenger or AIDL callback for progress
// updates.
}
@Override
public void onServiceDisconnected(ComponentName name) {
// The binding stays active and reconnects if the worker
// restarts.
downloadService = null;
}
};
@Override
protected void onStart() {
super.onStart();
Intent intent = new Intent(this, AssetDownloadService.class);
bindService(intent, downloadConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
if (downloadService != null) {
// Unregister the progress callback here, before unbinding.
downloadService = null;
}
// Unbind the ServiceConnection even if onServiceConnected hasn't run
// yet.
unbindService(downloadConnection);
}
}
Para saber mais sobre como as flags de vinculação afetam o oom_score_adj em todos os processos, consulte
Vinculações de serviço e estados de processo.
Coordenar o estado compartilhado no disco com segurança
Como o processo principal e o :bg são executados em espaços de endereço separados, com ambientes de execução e descritores de arquivo separados:
- SQLite e Room: use o registro prévio de escrita (o modo de diário padrão no
Room na maioria dos dispositivos) para que os dois processos possam ler e gravar no
banco de dados simultaneamente. Não chame
enableMultiInstanceInvalidationde:bg. O Room hospeda o serviço de invalidação no processo principal. Assim, o worker se vincula ao processo principal e mantém a memória da interface com a importância elevada do worker. Em vez disso, faça com que o worker informe as mudanças pelo retorno de chamada que a interface registra e faça com que a interface consulte novamente o banco de dados quando ele for iniciado. - Preferências: não compartilhe
SharedPreferencesentre processos. Use o DataStore em códigos com vários processos ou transmita o estado explicitamente pela IPC. - Arquivos de recursos: grave os pacotes de recursos baixados em um arquivo de preparo em
:bg, chamefdatasynce, em seguida,posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED)em:bgapós a gravação (conforme mostrado em Retornar páginas de memória para o SO) e renomeie o arquivo de forma atômica. Deixe que a UI colete o resultado na próxima vez que for iniciada e se vincule ao worker, em vez de chamar um processo de UI que pode estar congelado.
Observar o efeito no adb shell
Para verificar se a divisão dos processos em primeiro e segundo plano isola
o uso elevado de memória, inspecione os estados do processo do Activity Manager e
dumpsys meminfo depois de acessar a tela inicial. A saída de exemplo nesta
seção vem do app de exemplo MemoryLab, que executa a mesma alocação nativa de 64 MB
no processo de interface em ambas as configurações e hospeda o serviço em primeiro plano
dividido em um processo :remote.
1. Comparar estados de processo e pontuações de OOM
Inicie a carga de trabalho em segundo plano, pressione o botão home para que o Activity seja interrompido
e consulte o Activity Manager:
adb shell dumpsys activity processes com.example.app | grep -E "Proc #|oom:|state:"
Em uma arquitetura de processo único, o processo principal permanece elevado, mesmo que
o Activity seja interrompido. Por cerca de 15 segundos após o usuário sair, o Activity
Manager mantém um processo que executa um serviço em primeiro plano em oom_score_adj = 50
(fg-service-act) e o reduz para 200 (fg-service). A saída
abaixo foi capturada 20 segundos após o usuário pressionar o botão home:
Proc #1: prcp F/S/FGS ---NFUATI t: 0 13794:com.android.memorylab/u0a284 (fg-service)
oom: max=1001 curRaw=200 setRaw=200 cur=200 set=200
state: cur=FGS set=FGS lastRss=0.00 lastCachedRss=0.00
Em uma arquitetura de processo dividido, apenas o processo de worker permanece em
oom_score_adj = 200, enquanto o processo principal da interface cai para PREV
(oom_score_adj = 700) e, depois, para o bucket em cache
(oom_score_adj >= 900):
Proc #3: prcp F/S/FGS ---NFUATI t: 0 13544:com.android.memorylab:remote/u0a284 (fg-service)
oom: max=1001 curRaw=200 setRaw=200 cur=200 set=200
state: cur=FGS set=FGS lastRss=0.00 lastCachedRss=0.00
Proc #1: prev b/ /LAST -------TI t: 0 13312:com.android.memorylab/u0a284 (previous)
oom: max=1001 curRaw=700 setRaw=700 cur=700 set=700
state: cur=LAST set=LAST lastRss=0.00 lastCachedRss=0.00
Depois que o tempo limite do app anterior expira, o processo principal é movido para o bucket
em cache (cch, oom_score_adj = 900, previous-expired) e perde todos os
recursos de processo (---------), incluindo o tempo de CPU implícito (I). Nesse
momento, o sistema o congela e pode compactá-lo ou recuperá-lo a qualquer momento sem
interromper o processo de worker.
2. Comparar dumpsys meminfo entre processos
Compare o resumo do dumpsys meminfo do worker elevado
com o processo da interface:
adb shell dumpsys meminfo com.example.app:bg
adb shell dumpsys meminfo com.example.app
Exemplo de saída App Summary para o processo de worker do MemoryLab em
oom_score_adj = 200:
App Summary
Pss(KB) Rss(KB)
------ ------
Java Heap: 380 13840
Native Heap: 1548 2832
Code: 132 55816
Stack: 200 208
Graphics: 0 0
Private Other: 496
System: 1926
Unknown: 4168
TOTAL PSS: 4682 TOTAL RSS: 76864 TOTAL SWAP PSS: 441
Exemplo de saída de App Summary para o processo de IU do MemoryLab em
oom_score_adj = 700, que contém a alocação nativa de 64 MB em
Private Other:
App Summary
Pss(KB) Rss(KB)
------ ------
Java Heap: 1316 19816
Native Heap: 30408 31596
Code: 3148 115796
Stack: 656 664
Graphics: 0 0
Private Other: 67044
System: 13687
Unknown: 71092
TOTAL PSS: 116259 TOTAL RSS: 238964 TOTAL SWAP PSS: 259
O processo de worker precisa de menos de 5 MB de PSS, porque compartilha a maior parte
do código e das páginas de execução com o Zygote. Em um app que renderiza com Vulkan
ou OpenGL ES, a linha Graphics do processo de interface também inclui a memória da GPU.
Este exemplo foi capturado em um dispositivo virtual Cuttlefish com renderização
de software, então Graphics é 0 em ambos os processos.
3. Inspecionar a contabilização do cgroup por processo
Cada processo no seu UID tem o próprio diretório cgroup v2 dentro de um cgroup pai
para o app. O comando a seguir lista oom_score_adj,
memory.current e o memory.high de todo o app para cada processo do
app:
adb shell '
for PID in $(pidof com.example.app com.example.app:bg); do
CG=/sys/fs/cgroup$(sed -n "s/^0:://p" /proc/$PID/cgroup)
echo "pid=$PID ($(tr -d "\0" < /proc/$PID/cmdline))" \
"adj=$(cat /proc/$PID/oom_score_adj)" \
"memory.current=$(cat $CG/memory.current)" \
"app.memory.high=$(cat ${CG%/*}/memory.high)"
done
'
Exemplo de saída do MemoryLab depois de pressionar "Início":
pid=15301 (com.android.memorylab) adj=700 memory.current=103260160 app.memory.high=966787072
pid=16518 (com.android.memorylab:remote) adj=200 memory.current=3608576 app.memory.high=966787072
O processo de worker é cobrado em menos de 4 MB, enquanto o processo de interface é
cobrado em cerca de 98 MB. Cada processo tem o próprio memory.current, mas o
limitador de memória define o limite no cgroup pai que contém todos os processos
do app. Ambas as linhas mostram o mesmo app.memory.high de 966.787.072 bytes,
que é o limite não visível de 922 MB na saída a seguir.
Para ver os limites visíveis e não visíveis que o Limitador de Memória aplica ao dispositivo, execute o seguinte comando:
adb shell am memory-limiter status
Exemplo de saída de um dispositivo com 4 GB de RAM, em que o limite não visível é metade do limite visível:
Memory limiter
enabled limits=true killing=true ignored=none
visible: memHigh=1843MB memMax=2048MB swapMax=1024MB
not-visible: memHigh=922MB memMax=1024MB swapMax=1024MB
Observar o efeito no Perfetto
O Perfetto permite visualizar a linha do tempo de oom_score_adj e RSS nos dois
processos enquanto o usuário sai da IU em primeiro plano para a tela inicial, enquanto
o trabalho em segundo plano continua.
1. Gravar um rastro
Registre um rastro de 15 segundos que capture contadores de memória de processo, memória de GPU e
mudanças de oom_score_adj ao iniciar um job em segundo plano no app e
navegar até a tela inicial:
adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/process-splitting.perfetto-trace <<'EOF'
buffers: {
size_kb: 32768
fill_policy: DISCARD
}
data_sources: {
config {
name: "linux.process_stats"
process_stats_config {
scan_all_processes_on_start: true
proc_stats_poll_ms: 250
}
}
}
data_sources: {
config {
name: "android.gpu.memory"
}
}
data_sources: {
config {
name: "linux.ftrace"
ftrace_config {
ftrace_events: "oom/oom_score_adj_update"
ftrace_events: "gpu_mem/gpu_mem_total"
}
}
}
duration_ms: 15000
EOF
adb pull /data/misc/perfetto-traces/process-splitting.perfetto-trace
2. Inspecionar faixas de processo na interface do Perfetto
Abra o rastro na interface do Perfetto e expanda os grupos de processos
para com.example.app e com.example.app:bg:
- Fixe as faixas
oom_score_adjemem.rss.anonpara os dois processos para que elas apareçam lado a lado na parte de cima da linha do tempo. - Localize o carimbo de data/hora em que o usuário pressiona "Início":
- No processo de worker,
oom_score_adjpermanece em200emem.rss.anonpermanece estável e pequeno. - No processo da interface, o
oom_score_adjsobe de0para200, permanece lá por cerca de um segundo enquanto oActivityé interrompido e, em seguida, sobe para700(PREV). Após o tempo limite do app anterior, ele é movido para o intervalo em cache, em que o sistema pode compactar e recuperar a memória sem afetar o worker.
- No processo de worker,
Na Figura 2, o processo de IU permanece brevemente em 200 enquanto o Activity é interrompido
e, em seguida, sobe para 700, enquanto o worker permanece em 200 com cerca de 19 MB
de RSS anônimo. Para comparação, a Figura 3 mostra um rastro de 35 segundos da
configuração de colocalização, em que o processo único permanece em 50 e, em seguida, em 200
com toda a memória da interface.
Para conferir essas visualizações, abra o trace dividido e o trace colocalizado na interface do Perfetto.
3. Consultar o consumo de memória elevado com o PerfettoSQL
Execute a seguinte consulta PerfettoSQL na guia Query (SQL) para medir o
RSS anônimo e de arquivo médio mantido por cada processo do app em cada
nível oom_score_adj:
INCLUDE PERFETTO MODULE android.memory.process;
SELECT
process_name,
score AS oom_score_adj,
ROUND(SUM(dur) / 1e9, 2) AS duration_sec,
ROUND(SUM(anon_rss * dur) / SUM(dur) / 1048576.0, 1) AS avg_anon_mb,
ROUND(SUM(file_rss * dur) / SUM(dur) / 1048576.0, 1) AS avg_file_mb
FROM memory_oom_score_with_rss_and_swap_per_process
WHERE process_name GLOB 'com.example.app*'
GROUP BY process_name, score
ORDER BY process_name, score;
Exemplo de saída para o trace dividido na Figura 2:
process_name oom_score_adj duration_sec avg_anon_mb avg_file_mb
com.android.memorylab 0 4.11 93.3 110.4
com.android.memorylab 200 1.06 126.8 110.5
com.android.memorylab 700 9.72 115.1 110.5
com.android.memorylab:remote 0 0.01 NULL NULL
com.android.memorylab:remote 200 12.87 18.9 53.7
Exemplo de saída para o trace colocalizado na Figura 3:
process_name oom_score_adj duration_sec avg_anon_mb avg_file_mb
com.android.memorylab 0 4.10 96.0 110.4
com.android.memorylab 50 16.04 117.6 110.5
com.android.memorylab 200 14.68 116.5 110.6
Na configuração dividida, apenas cerca de 19 MB de memória anônima permanecem em
oom_score_adj = 200 após o primeiro segundo. Na configuração de colocalização,
cerca de 117 MB permanecem em 50 e, em seguida, em 200 enquanto o serviço em primeiro plano
estiver em execução.
← Retornar páginas de memória para o SO | ↑ Para cima