Molte app eseguono operazioni legittime quando la loro UI non è più visibile, ad esempio
il download di pacchetti di asset di grandi dimensioni, la sincronizzazione di database locali, lo streaming audio,
la registrazione di allenamenti o il mantenimento di una sessione di un dispositivo connesso. Quando questo lavoro in background viene eseguito all'interno dello stesso processo del sistema operativo della UI in primo piano e del motore grafico, l'intero processo rimane a un livello di importanza elevato (oom_score_adj tra 0 e 250) insieme a tutta la memoria allocata dalla UI.
Questa pagina spiega perché questo danneggia la tua app e il resto del dispositivo, come spostare il lavoro in background in un processo leggero dedicato utilizzando android:process e come verificare il risparmio di memoria utilizzando adb shell e Perfetto.
Perché il lavoro su un singolo processo conserva la memoria della UI
Android gestisce la memoria e la terminazione dei processi con la granularità di un processo del sistema operativo (pid), non dei singoli componenti (Activity, Service o BroadcastReceiver). Activity Manager (OomAdjuster) assegna a ogni processo un singolo valore oom_score_adj che riflette il componente attivo o l'associazione in entrata più importante in quel processo:
- Quando l'utente interagisce con la tua
Activity, il processo viene eseguito nello statoTOP(oom_score_adj = 0). - Quando l'utente esce dall'app e questa non ha componenti in background attivi, il processo passa al bucket
PREV(oom_score_adj = 700) oCACHED(oom_score_adj >= 900). Nel bucket memorizzato nella cache, la piattaforma blocca il processo con il freezer cgroup v2, compatta la memoria conCachedAppOptimizere consente almkddi terminare il processo per primo in caso di pressione della memoria. - Tuttavia, se lo stesso processo esegue anche un servizio in primo piano (ad esempio
i tipi
dataSync,mediaPlayback,connectedDeviceolocationinoom_score_adj = 200) o ospita un servizio a cui si associa il sistema o un altro client importante, l'intero processo rimane con quella priorità elevata anche dopo l'arresto diActivity.
Quando un processo UI pesante rimane a oom_score_adj = 200, diverse categorie di memoria di grandi dimensioni rimangono residenti:
Activityinterrotta e gerarchia di oggetti View: a meno che l'Activitynon venga terminata esplicitamente, il relativoWindow, l'albero delle visualizzazioni, le strutture di layout e lo stato della visualizzazione rimangono raggiungibili nell'heap Java mentre l'utente si trova in un'altra app.- Contesti grafici nativi e allocazioni GPU: l'
HardwareRendererstandard mantiene il contesto e le cache della GPU, mentre unActivityinterrotto si trova in un processo non memorizzato nella cache. Anche le app e i motori grafici che creano i propri contesti Vulkan (VkDevice,VkSwapchainKHR, pool di descrittori, cache della pipeline) o OpenGL ES (EGLContext, texture, buffer dei vertici) mantengono le strutture dei driver, i programmi shader e i buffer grafici, a meno che il motore non elimini esplicitamente il dispositivo grafico quando viene arrestato. - Frammentazione dell'heap e overhead di runtime: anche quando un'app rilascia le cache in
onTrimMemory, le pagine dell'allocatore nativo parzialmente popolate, le pagine di copia su scrittura Zygote dirty, le librerie condivise caricate (.so) e i metadati di runtime rimangono residenti nella memoria privata del processo. Poiché il processo supera la soglia di memorizzazione nella cache (oom_score_adj < 900), la piattaforma non lo blocca né esegue la compattazione delle app memorizzate nella cache. Per un'analisi più approfondita di questo effetto con i servizi associati, consulta Collocazione di un servizio associato con un'interfaccia utente che richiede molta memoria.
In che modo un processo in background snello protegge la tua app
Mantenere in background una memoria di dimensioni adatte al primo piano mette a rischio il lavoro in background:
- I limiti di memoria in background sono più rigidi di quelli in primo piano. In Android 17 e versioni successive, il limitatore di memoria applica alla tua app un limite di memoria non visibile più piccolo, mentre nessuno dei suoi processi mostra una UI visibile, anche durante l'esecuzione di un servizio in primo piano. Il limite copre tutti i processi della tua app. Se il processo che esegue il servizio in primo piano conserva ancora la memoria della UI e della grafica, questa memoria viene conteggiata ai fini del limite inferiore. Quando l'app supera il limite, il kernel la limita durante il recupero delle pagine, il che rallenta il download, la sincronizzazione o la riproduzione in esecuzione. Se l'app continua ad allocare memoria dopo l'esaurimento dello swap, il sistema la termina. Per verificare se la tua app è stata interessata, consulta Limiti di memoria dell'app.
- I processi più grandi vengono terminati per primi in caso di pressione sulla memoria. Quando
lmkddeve liberare memoria ed è configurato per scegliere il processo più pesante in un bucket di importanza (ro.lmk.kill_heaviest_task), un processo di servizio in primo piano che contiene anche centinaia di MB di memoria UI viene terminato prima di processi più piccoli con la stessa importanza e il tuo lavoro in background si interrompe con questo. Consulta Interazione tra kswapd e lmkd. - Lo stato della UI memorizzato nella cache può essere bloccato, compattato e riscritto nella memoria flash. Consentire allo stato della UI in primo piano di passare a un processo memorizzato nella cache consente al sistema operativo di bloccare il processo, comprimere la memoria in zRAM e riscrivere le pagine cold nello spazio di archiviazione flash. Il footprint della memoria residente si riduce mentre lo stato della UI rimane invariato, offrendo sia al lavoro in background sia alla UI memorizzata nella cache la migliore possibilità di sopravvivere fino al ritorno dell'utente.
- I budget di memoria per processo diventano pratici. Un processo che esegue solo lavoro in background ha un working set ridotto e prevedibile, quindi puoi assegnargli un budget di memoria limitato senza vincolare la UI.
L'esecuzione di un secondo processo potrebbe sembrare un aumento del footprint della memoria totale dell'app, perché un secondo runtime ha un costo di base ridotto. Questo confronto è valido solo se un singolo processo può liberare completamente tutto lo stato della UI e della grafica ogni volta che passa al lavoro in background, cosa che le app e i toolkit della UI non possono fare in modo affidabile nella pratica. Consentire al sistema operativo di bloccare, compattare e riscrivere un processo UI memorizzato nella cache separato utilizza molta meno memoria fisica nel caso comune, mantiene l'app entro il limite di non visibilità e riduce le terminazioni per memoria insufficiente per l'app e per l'intero dispositivo.
Isola il lavoro in background in un processo dedicato
Per eliminare la residenza di UI e grafica durante il lavoro in background, esegui componenti in background di lunga durata in un processo leggero dedicato, mantenendo l'UI e il motore grafico nel processo principale:
| Processo | Dichiarazione del manifest | Componenti in hosting | Stato quando l'utente esce dalla UI durante il lavoro in background |
|---|---|---|---|
| Processo UI principale | Valore predefinito (com.example.app) |
Activity, visualizzazioni UI, cache delle immagini, renderer Vulkan o OpenGL ES, motore audio e di scena
|
Scende a PREV e poi a CACHED
(oom_score_adj >= 900). Bloccato, compattato e riscritto
nella memoria flash.
|
| Processo worker in background | android:process=":bg" |
Service in primo piano, downloader di asset in background, worker di sincronizzazione o gestore di dispositivi connessi
|
Viene eseguito a FGS (oom_score_adj = 200) solo con l'heap e il codice del worker, senza visualizzazioni UI o contesti GPU.
|
Dichiara i budget di processo e di memoria
Assegna il tuo background Service a un sottoprocesso privato impostando
android:process=":bg" in AndroidManifest.xml. I due punti iniziali (:) creano un processo privato con ambito limitato all'UID della tua app.
In Android 17 QPR2 (livello API 37.2) e versioni successive, combina la suddivisione dei processi con i budget di memoria dell'app per processo all'interno del blocco <processes> in modo che il sistema operativo imponga un limite massimo di working set per il worker in background senza limitare il processo della UI in primo piano:
<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>
La memoria utilizzata in :bg viene conteggiata sia nel budget di
sia nel budget dell'app. Sui dispositivi con versioni precedenti ad Android 17 QPR2, il sistema ignora <memory-budget>, mentre android:process=":bg" funziona su tutti i livelli API.
Inizializzazione del gate in Application.onCreate
Android crea un'istanza della classe Application e chiama Application.onCreate
in ogni processo generato per il tuo pacchetto. Un errore comune quando si adotta
android:process=":bg" è lasciare toolkit UI pesanti, pool di caricamento delle immagini,
SDK Analytics o l'inizializzazione del motore nativo (System.loadLibrary) all'interno di un
metodo Application.onCreate non protetto. In questo modo, il processo :bg viene gonfiato con lo stesso footprint di base che stai cercando di evitare.
Controlla Application.getProcessName, disponibile in Android 9 (livello API 28) e versioni successive, all'interno di onCreate e inizializza solo le dipendenze richieste dal processo corrente:
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");
}
}
Le librerie che vengono inizializzate tramite un provider di contenuti, come Jetpack App Startup, vengono inizializzate solo nel processo principale, perché un provider di contenuti dichiarato senza android:process viene eseguito nel processo principale. Se il codice in :bg dipende da una libreria di questo tipo, inizializzala esplicitamente in initBackgroundWorkerProcess.
Se utilizzi un motore grafico come Unity, implementa il servizio in background in una libreria o un plug-in Android semplice scritto in Java o Kotlin che non faccia riferimento a classi del motore come UnityPlayer. Il processo :bg non carica mai le librerie native del motore. Per confermare, verifica che
adb shell cat /proc/<pid>/maps per il processo :bg non elenchi
libunity.so o libil2cpp.so.
Comunicare senza bloccare il processo della UI
Quando l'utente ha MainActivity aperto mentre AssetDownloadService è in esecuzione in
:bg, la UI spesso deve mostrare l'avanzamento del download in tempo reale. L'importanza del processo
passa da un client al processo che ospita il servizio o il provider di contenuti
che utilizza, quindi la direzione di ogni dipendenza è importante:
- L'associazione della UI al worker è sicura. Quando
MainActivitysi associa aAssetDownloadService, il sistema aumenta l'importanza di:bgin modo che corrisponda a quella della UI visibile. Quando l'utente esce, il processo della UI viene memorizzato nella cache e l'associazione non eleva più nulla. - Il worker che dipende dal processo della UI non è sicuro. Se il codice in
:bgsi associa a un servizio o esegue query su un provider di contenuti eseguito nel processo principale, il sistema eleva il processo principale all'importanza elevata del worker e la memoria della UI viene nuovamente bloccata. Fai in modo che la UI registri un callback con il worker, ad esempio unMessengero un'interfaccia di callback AIDL. Il passaggio di un oggetto binder come callback non modifica l'importanza di nessuno dei due processi.
Annulla la registrazione del callback prima di annullare l'associazione, in modo che il worker non chiami mai il processo della UI dopo che il sistema lo ha bloccato. L'esempio seguente esegue l'associazione al worker solo quando la UI è visibile:
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);
}
}
Per saperne di più su come i flag di associazione influiscono su oom_score_adj tra i processi, consulta
Associazioni di servizi e stati dei processi.
Coordinare lo stato condiviso su disco in modo sicuro
Poiché il processo principale e :bg vengono eseguiti in spazi di indirizzi separati con runtime e descrittori dei file separati:
- SQLite e Room: utilizza il logging write-ahead (la modalità di journal predefinita in Room sulla maggior parte dei dispositivi) in modo che entrambi i processi possano leggere e scrivere nel database contemporaneamente. Non chiamare
enableMultiInstanceInvalidationda:bg. Room ospita il proprio servizio di invalidazione nel processo principale, quindi il worker si associa al processo principale e mantiene la memoria della UI con l'importanza elevata del worker. Invece, fai in modo che il worker segnali le modifiche tramite il callback registrato dalla UI e che la UI esegua nuovamente una query sul database all'avvio. - Preferenze: non condividere
SharedPreferencestra i processi. Utilizza DataStore nel codice multiprocesso o passa lo stato esplicitamente tramite IPC. - File degli asset: scrivi i pacchetti di asset scaricati in un file di gestione temporanea in
:bg, chiamafdatasynce poiposix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED)in:bgdopo la scrittura (come mostrato in Restituisci le pagine di memoria al sistema operativo) e rinomina il file in modo atomico. Lascia che l'interfaccia utente acquisisca il risultato al successivo avvio e associazione al worker, anziché chiamare un processo dell'interfaccia utente che potrebbe essere bloccato.
Osserva l'effetto nella shell adb
Puoi verificare che la suddivisione dei processi in primo piano e in background isoli l'utilizzo elevato della memoria esaminando gli stati dei processi di Activity Manager e dumpsys meminfo dopo aver eseguito la navigazione alla schermata Home. L'output di esempio in questa sezione proviene dall'app di esempio MemoryLab, che esegue la stessa allocazione nativa di 64 MB nel processo UI in entrambe le configurazioni e ospita il servizio in primo piano diviso in un processo :remote.
1. Confronta gli stati dei processi e i punteggi OOM
Avvia il workload in background, premi il pulsante Home in modo che Activity si interrompa
ed esegui una query su Activity Manager:
adb shell dumpsys activity processes com.example.app | grep -E "Proc #|oom:|state:"
In un'architettura a processo singolo, il processo principale rimane elevato anche se il suo Activity viene interrotto. Per circa 15 secondi dopo che l'utente esce, Activity
Manager mantiene un processo che esegue un servizio in primo piano su oom_score_adj = 50
(fg-service-act) e poi lo abbassa su 200 (fg-service). Il seguente
output è stato acquisito 20 secondi dopo aver premuto 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
In un'architettura a processi separati, solo il processo worker rimane in
oom_score_adj = 200, mentre il processo UI principale scende a PREV
(oom_score_adj = 700) e successivamente al bucket memorizzato nella 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
Una volta scaduto il timeout dell'app precedente, il processo principale passa al bucket memorizzato nella cache (cch, oom_score_adj = 900, previous-expired) e perde tutte le sue funzionalità di processo (---------), incluso il tempo di CPU implicito (I). A quel punto, il sistema lo blocca e può compattarlo o recuperarlo in qualsiasi momento senza interrompere il processo worker.
2. Confronta dumpsys meminfo tra i processi
Confronta il riepilogo di dumpsys meminfo del worker con privilegi elevati con il processo della UI:
adb shell dumpsys meminfo com.example.app:bg
adb shell dumpsys meminfo com.example.app
Esempio di output App Summary per il processo worker MemoryLab all'indirizzo 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
Output di esempio di App Summary per il processo della UI di MemoryLab in oom_score_adj = 700, che contiene l'allocazione nativa di 64 MB in 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
Il processo worker richiede meno di 5 MB di PSS, perché condivide la maggior parte delle sue pagine di codice e di runtime con Zygote. In un'app che esegue il rendering con Vulkan o OpenGL ES, la riga Graphics del processo UI include anche la memoria GPU.
Questo esempio è stato acquisito su un dispositivo virtuale Cuttlefish con rendering software, quindi Graphics è 0 in entrambi i processi.
3. Ispeziona l'accounting cgroup per processo
Ogni processo con il tuo UID ha la propria directory cgroup v2 all'interno di un cgroup principale per la tua app. Il seguente comando elenca oom_score_adj, memory.current e memory.high a livello di app per ogni processo della tua 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
'
Esempio di output per MemoryLab dopo aver premuto Home:
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
Al processo worker vengono addebitati meno di 4 MB, mentre al processo UI vengono addebitati circa 98 MB. Ogni processo ha il proprio memory.current, ma il limitatore di memoria imposta il limite sul cgroup principale che contiene tutti i processi della tua app. Entrambe le righe mostrano lo stesso app.memory.high di 966.787.072 byte, ovvero il limite non visibile di 922 MB nell'output seguente.
Per visualizzare i limiti visibili e non visibili applicati dal limitatore di memoria sul dispositivo, esegui questo comando:
adb shell am memory-limiter status
Output di esempio da un dispositivo con 4 GB di RAM, in cui il limite non visibile è la metà del limite visibile:
Memory limiter
enabled limits=true killing=true ignored=none
visible: memHigh=1843MB memMax=2048MB swapMax=1024MB
not-visible: memHigh=922MB memMax=1024MB swapMax=1024MB
Osserva l'effetto in Perfetto
Perfetto ti consente di visualizzare la cronologia di oom_score_adj e RSS in entrambi i processi mentre l'utente passa dalla UI in primo piano alla schermata Home mentre il lavoro in background continua.
1. Registra una traccia
Registra una traccia di 15 secondi che acquisisce i contatori della memoria di processo, la memoria della GPU e le modifiche di oom_score_adj durante l'avvio di un job in background nella tua app e la navigazione alla schermata Home:
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. Ispeziona le tracce dei processi nella UI di Perfetto
Apri la traccia nell'interfaccia utente di Perfetto ed espandi i gruppi di processi per com.example.app e com.example.app:bg:
- Blocca le tracce
oom_score_adjemem.rss.anonper entrambi i processi in modo che vengano visualizzate una accanto all'altra nella parte superiore della cronologia. - Individua il timestamp in cui l'utente preme Home:
- Nel processo worker,
oom_score_adjrimane a200emem.rss.anonrimane piatto e piccolo. - Nel processo UI,
oom_score_adjsale da0a200, rimane lì per circa un secondo mentreActivitysi arresta, quindi sale a700(PREV). Dopo il timeout dell'app precedente, passa all'intervallo memorizzato nella cache, dove il sistema può compattare e recuperare la memoria senza influire sul worker.
- Nel processo worker,
Nella Figura 2, il processo UI rimane brevemente a 200 mentre il suo Activity si interrompe
e poi sale a 700, mentre il worker rimane a 200 con circa 19 MB
di RSS anonimo. Per un confronto, la Figura 3 mostra una traccia di 35 secondi della configurazione in co-location, in cui il singolo processo rimane a 50 e poi a 200 con tutta la memoria della UI.
Per esplorare queste visualizzazioni, apri la traccia divisa e la traccia in co-location nell'interfaccia utente di Perfetto.
3. Esegui query sul footprint della memoria elevato con PerfettoSQL
Esegui la seguente query PerfettoSQL nella scheda Query (SQL) per misurare l'RSS anonimo e dei file medio di ogni processo della tua app a ogni livello 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;
Esempio di output per la traccia divisa nella 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
Esempio di output per la traccia in co-localizzazione nella 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
Nella configurazione divisa, solo circa 19 MB di memoria anonima rimangono in oom_score_adj = 200 dopo il primo secondo. Nella configurazione co-localizzata, circa 117 MB rimangono in 50 e poi in 200 per tutto il tempo in cui il servizio in primo piano è in esecuzione.
← Restituisci le pagine di memoria al sistema operativo | ↑ Su