I processi delle app su Android non esistono in modo isolato. Le applicazioni spesso si basano su servizi forniti da altre applicazioni o dal sistema stesso. Quando un processo si connette a un altro tramite un Service Binding, crea una dipendenza che ha un profondo impatto sul modo in cui il framework Android gestisce la memoria.
Stati del processo e punteggi OOM
Il framework Android utilizza gli stati del processo per monitorare l'importanza di ogni processo in esecuzione. Questi stati vengono poi utilizzati dal OomAdjuster per assegnare un valore di aggiustamento del punteggio OOM (oom_score_adj) compreso tra -1000 e 1000.
Un valore di oom_score_adj inferiore indica che il processo è più importante e meno probabile che venga interrotto da Low Memory Killer (LMK).
Stati processo comuni
La tabella seguente mostra alcuni degli stati di elaborazione più comuni e i relativi
valori oom_score_adj tipici. Per un elenco completo e aggiornato, consulta
android.app.ActivityManager e com.android.server.am.psc.Constants nel
codice sorgente di Android.
| Stato processo (abbreviazione) | Descrizione | Valore tipico: oom_score_adj |
|---|---|---|
| PER (persistente) | Processi di sistema che devono essere sempre in esecuzione (ad es. Telefonia). | -800 |
| TOP | Il processo con cui l'utente sta interagendo. | 0 |
| VIS (visibile) | Il processo ha un'attività visibile (ad es. dietro una finestra di dialogo traslucida). | 100 |
| PERC (Perceptible) | Processo in background di cui l'utente è a conoscenza (ad es. riproduzione di musica). | 200 |
| FGS | Processo di hosting di un servizio in primo piano. | Da 0 a 200 (varia) |
| BTOP (Bound Top) | Processo vincolato da un'applicazione TOP. | 100 |
| BFGS | Servizio in primo piano associato (in genere associato al sistema). | 0 |
| PREV (Indietro) | L'ultimo processo in cui si trovava l'utente prima di quello attuale. | 700 |
| COPIA CACHE | App in background che possono essere chiuse in modo sicuro. | Da 900 a 999 |
Impatto delle associazioni dei servizi
Quando un processo client (ad es. un'app nello stato TOP) si associa a un servizio in un processo server, quest'ultimo spesso eredita una priorità elevata. In questo modo, il servizio rimane disponibile finché il cliente ne ha bisogno.

Controllare l'ereditarietà con i flag BIND
L'ereditarietà è il comportamento predefinito quando utilizzi Context.BIND_AUTO_CREATE.
Tuttavia, gli sviluppatori possono controllare in che modo il binding influisce sull'importanza del processo di destinazione utilizzando vari flag in bindService().
Flag BIND chiave per il punteggio OOM
I seguenti flag sono più pertinenti quando si gestisce la pressione della memoria a livello di sistema:
BIND_AUTO_CREATE: il flag più comune. Garantisce che il processo di servizio venga avviato e mantenuto attivo finché esiste il binding. Per impostazione predefinita, aumenta anche la priorità del processo del server in modo che corrisponda a quella del client.BIND_NOT_FOREGROUND: impedisce che la priorità di pianificazione (priorità della CPU) del processo del servizio di destinazione venga aumentata al primo piano. Tuttavia, consente comunque di aumentare la priorità della memoria (oom_score_adj). Questo è utile per il lavoro in background che non deve competere con la UI per i cicli della CPU, ma deve comunque essere protetto dalla chiusura.BIND_WAIVE_PRIORITY: un flag molto forte che indica al sistema di non influire sulla priorità di pianificazione o di gestione della memoria del processo di destinazione. Il processo di servizio verrà gestito come se fosse un normale processo in background nell'elenco LRU, rendendolo idoneo all'interruzione per esaurimento della memoria anche durante l'associazione.BIND_ABOVE_CLIENT: indica che il servizio è più importante dell'app client stessa. Quando il sistema deve recuperare memoria, preferisce terminare l'app client prima di terminare il servizio associato. È "più forte" diBIND_AUTO_CREATEperché fornisce un ulteriore livello di protezione per il servizio a spese del client.BIND_NOT_PERCEPTIBLE: riduce l'importanza del servizio di destinazione al di sotto del livelloPERCEPTIBLE, consentendo al sistema di recuperare la memoria per fare spazio a processi più critici percepibili dall'utente.
Attività pratica: osservare gli effetti del binding
Utilizzeremo l'applicazione MemoryLab per mostrare come un binding di un'app
TOP influisce sullo stato di un processo separato.
1. Avvia MemoryLab
Il seguente comando avvia l'app. Dopo l'apertura, assicurati che l'app rimanga in primo piano (non premere ancora il tasto Home o cambiare app).
adb shell am start -n com.android.memorylab/.MainActivity
2. Identificare i processi
Controlla gli stati del processo prima dell'associazione. MemoryLab esegue la sua UI principale in un processo e ha un RemoteService che viene eseguito in un processo :remote.
adb shell dumpsys activity processes com.android.memorylab
Esempio di snippet di output:
Process OOM control (154 total, non-act at 7, non-svc at 7):
Proc #0: fg T/A/TOP LCMNFUATI t: 0 13470:com.android.memorylab/u0a417 (top-activity)
oom: max=1001 curRaw=0 setRaw=0 cur=0 set=0
state: cur=TOP set=TOP lastRss=0.00 lastCachedRss=0.00
Vedrai il processo principale com.android.memorylab nello stato TOP. La procedura
:remote non è ancora iniziata.
3. Associazione di trigger
Invia una trasmissione all'app per attivare l'associazione dei servizi:
adb shell am broadcast -a com.android.memorylab.LEAK_BINDER
4. Osserva lo stato elevato
Controlla di nuovo gli stati del processo:
adb shell dumpsys activity processes | grep -A 10 "com.android.memorylab"
Esempio di snippet di output:
Proc # 1: vis F/ /BTOP ---NFUATI t: 0 13560:com.android.memorylab:remote/u0a417 (service)
com.android.memorylab/.RemoteService<=Proc{13470:com.android.memorylab/u0a417}
oom: max=1001 curRaw=100 setRaw=100 cur=100 set=100
state: cur=BTOP set=BTOP lastRss=0.00 lastCachedRss=0.00
Il processo :remote è ora in esecuzione e nello stato BTOP (Bound TOP) con
un oom_score_adj di 100. È molto più protetto di un
tipico servizio in background (che sarebbe a 500 o superiore). La notazione
<=Proc{...} mostra il processo responsabile di questo aumento di priorità.
5. Invia in background
Premi il pulsante HOME sul dispositivo. Controlla di nuovo gli stati:
adb shell dumpsys activity processes | grep -A 10 "com.android.memorylab"
Esempio di snippet di output:
Proc # 2: prev b/ /LAST --------I t: 0 13560:com.android.memorylab:remote/u0a417 (service)
com.android.memorylab/.RemoteService<=Proc{13470:com.android.memorylab/u0a417}
oom: max=1001 curRaw=700 setRaw=700 cur=700 set=700
state: cur=LAST set=LAST lastRss=0.00 lastCachedRss=0.00
Proc # 1: prev b/ /LAST --------I t: 0 13470:com.android.memorylab/u0a417 (previous)
oom: max=1001 curRaw=700 setRaw=700 cur=700 set=700
state: cur=LAST set=LAST lastRss=209MB lastCachedRss=0.00
Ora entrambi i processi sono passati a uno stato di priorità inferiore (PREV /
oom_score_adj 700), perché il processo client non è più TOP. (Nota:
LAST nel dump dello stato si riferisce allo stato interno LAST_ACTIVITY, che
corrisponde a PREV nei riepiloghi di alto livello).
Analisi con procstats
Lo strumento procstats fornisce una visualizzazione cronologica di questi stati.
# View stats for MemoryLab over the last hour
adb shell dumpsys procstats --hours 1 com.android.memorylab
Esempio di snippet di output:
* com.android.memorylab / u0a417 / v37:
* Prc com.android.memorylab / u0a417 / v37:
TOTAL: 0.89% (0.00-0.00-0.00/0.00-0.00-0.00/210MB-210MB-210MB over 1)
Top: 0.89% (0.00-0.00-0.00/0.00-0.00-0.00/210MB-210MB-210MB over 1)
* Prc com.android.memorylab:remote / u0a417 / v37:
TOTAL: 0.19%
Bnd Top: 0.19%
In questo caso, Bnd Top indica la percentuale di tempo in cui il processo remoto è stato vincolato da un'applicazione nello stato TOP.
Acquisizione e analisi dei binding con Perfetto
dumpsys ti offre un'istantanea, mentre Perfetto ti consente di vedere il momento esatto in cui si verifica un binding e come cambia il punteggio OOM in tempo reale.
1. Registrare una traccia
Utilizza una configurazione che includa linux.process_stats e la categoria atrace am:
adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/service_bindings.perfetto-trace <<EOF
buffers: { size_kb: 65536 }
data_sources: {
config {
name: "linux.process_stats"
process_stats_config { proc_stats_poll_ms: 100 }
}
}
data_sources: {
config {
name: "linux.ftrace"
ftrace_config { ftrace_events: "am/am_proc_bound" }
}
}
duration_ms: 15000
EOF
2. Transizioni del punteggio OOM delle query
Utilizzando PerfettoSQL, puoi vedere come è cambiato il punteggio OOM del processo remoto rispetto al processo dell'interfaccia utente:
SELECT ts, p.name, value AS oom_score_adj
FROM counter c
JOIN process_counter_track t ON c.track_id = t.id
JOIN process p USING (upid)
WHERE p.name LIKE 'com.android.memorylab%'
AND t.name = 'oom_score_adj'
ORDER BY ts;
3. Identificare gli eventi di binding
Per vedere esattamente quando è stata stabilita una dipendenza di binding e quale processo l'ha avviata, utilizza questa query:
SELECT
s.ts,
p.name AS process_name,
t.name AS thread_name,
s.name AS slice_name
FROM slice s
JOIN thread_track tt ON s.track_id = tt.id
JOIN thread t USING (utid)
JOIN process p USING (upid)
WHERE s.name LIKE 'bindService:{com.android.memorylab%';
Associazioni da sistema ad app
Il sistema Android stesso spesso si lega ai servizi nelle app di terze parti per fornire funzionalità di base. Spesso l'obiettivo di queste associazioni è la riduzione della latenza. Mantenendo un processo attivo e in memoria, il sistema evita il costoso overhead di un "avvio a freddo" (caricamento dell'APK, inizializzazione del runtime e creazione dell'oggetto Application) quando si verifica un'interazione utente critica. Esistono altri binding per evitare avvii a freddo frequenti per le app che devono gestire flussi di eventi in background.
Ecco alcuni esempi reali che puoi osservare su un dispositivo tipico:
VoiceInteractor
Gli utenti si aspettano che un assistente digitale sia integrato nel sistema operativo del proprio smartphone, di poterlo richiamare istantaneamente con una hotword vocale o un gesto di input rapido e che l'interazione sia fluida e senza interruzioni.
Quando si verifica un trigger dell'assistente (ad esempio la hotword "Hey Google" sugli smartphone Google Pixel), l'assistente digitale deve rispondere immediatamente. Per garantire questo,
system_server mantiene un binding permanente con il servizio di interazione vocale selezionato dall'utente.

Se controlli gli stati dei processi (ad es. utilizzando dumpsys activity processes), potresti visualizzare un processo come com.google.android.googlequicksearchbox:interactor nello stato BFGS (servizio di primo piano associato), mantenuto attivo da un'associazione da system_server (UID 1000).
NotificationListenerService
Per alcuni binding da sistema ad app, l'obiettivo non è la latenza, ma piuttosto la prevenzione
di frequenti avvii a freddo.
NotificationListenerService,
un servizio che riceve chiamate dal sistema quando vengono pubblicate
o rimosse nuove notifiche, è un ottimo esempio. Un tipico utente di smartphone può ricevere centinaia
di notifiche durante il giorno. Se il sistema si scollega da un listener di notifiche, il processo dell'app probabilmente passerà allo stato memorizzato nella cache e potrebbe essere interrotto da LMK.
Quando arriva la notifica successiva, potenzialmente pochi secondi dopo, il sistema sarebbe costretto ad avviare a freddo il processo dell'app da zero solo per inviare l'evento. Questo ciclo costante di interruzione e avvio a freddo consumerebbe molta più CPU e batteria rispetto al semplice mantenimento del processo associato e attivo in background.
La schermata -1 (feed di notizie) del launcher
Le app di avvio moderne in genere combinano la funzionalità di navigazione di base (icone e widget della home page) con un feed di notizie disponibile in una delle schermate di avvio e integrato perfettamente con l'esperienza utente di avvio. Il feed di notizie potrebbe essere fornito da un'altra app. Ad esempio, su Google Pixel il launcher si integra con un feed fornito dall'app Google.
Quando scorri verso sinistra sulla schermata Home per visualizzare il feed delle notizie, la transizione deve essere fluida. Il launcher lo fa eseguendo il binding a un'interfaccia di servizio nell'app che fornisce il feed di notizie e mantenendo attivo il binding finché il launcher è attivo. In questo modo, i contenuti del feed vengono sottoposti a rendering e sono pronti in memoria anche quando non li stai guardando.
Altri esempi comuni
- Avvio app (HOME_APP_ADJ): l'app di avvio app (Home) ha un proprio slot speciale nell'elenco delle priorità. Anche se non sempre vincolato a un servizio, gli viene assegnato il
HOME_APP_ADJ(in genere 600). Il sistema preferisce mantenere attivo il launcher, perché l'utente lo utilizza spesso. Infatti, il sistema preferisce chiudere l'app utilizzata in precedenza (PREV_APP_ADJ = 700) piuttosto che chiudere l'avvio app, perché la chiusura dell'avvio app comporterebbe un'esperienza utente lenta all'uscita da qualsiasi app, poiché l'utente dovrà attendere l'avvio a freddo dell'avvio app. - Input Method Editor (IME): durante la digitazione, il sistema si lega all'app tastiera scelta (ad es. Gboard). In questo modo, il processo della tastiera rimane in uno stato elevato anche se la tastiera viene temporaneamente nascosta. In questo modo, la tastiera può riapparire immediatamente quando tocchi un altro campo di testo.
- Pagamenti NFC: quando tocchi lo smartphone per pagare, il sistema si lega al servizio di pagamento NFC (ad es. Google Wallet). Queste transazioni spesso hanno requisiti rigorosi in tempo reale dal terminale del commerciante. Se l'app di pagamento avesse dovuto avviarsi a freddo, la transazione potrebbe scadere e non andare a buon fine.
Compromessi e calo delle prestazioni
Sebbene i binding siano necessari per le prestazioni e la correttezza, hanno un costo per l'integrità della memoria del sistema.
- Flessibilità ridotta: ogni processo vincolato è un processo che LMK non può terminare facilmente. In questo modo si riduce il "cuscinetto" di processi memorizzati nella cache che il sistema può utilizzare per liberare memoria sotto pressione.
- Aggravamento del calo delle prestazioni: se sono associati troppi processi, il sistema potrebbe ritrovarsi con quasi nessun processo in background terminabile. Quando la pressione della memoria aumenta, il sistema "precipita" molto più velocemente, poiché è costretto a terminare processi più importanti o a svuotare la cache delle pagine.
Anti-pattern comuni di associazione dei servizi
Poiché i service binding aumentano direttamente oom_score_adj, piccoli errori del ciclo di vita
nel modo in cui acquisisci o strutturi i binding possono bloccare grandi quantità di
memoria in stati privilegiati (BTOP, BFGS o PERC) per ore. Fai attenzione
a questi anti-pattern comuni durante la progettazione o il controllo dei servizi associati.
unbindService() dimenticato nei client di lunga durata
Il binding a un servizio da un singleton Application, un gestore in background o un Activity che chiama bindService() in onStart() senza un unbindService() corrispondente in onStop() comporta una perdita di ServiceConnection.
Finché questo binding rimane attivo, il processo di destinazione eredita la
priorità elevata del client. Se il client è un componente di sistema persistente o un'app in primo piano, il processo del servizio associato rimane bloccato in BFGS o BTOP
indefinitamente (mostrando un valore vicino al 100% in procstats), impedendo al LMK di
recuperare la memoria anche quando il servizio è completamente inattivo.
Soluzione: limita i binding dell'ambito al ciclo di vita del componente che
li richiede oppure implementa un timeout di inattività che chiama unbindService() dopo un
periodo di inattività. Evita di annullare e ripetere l'associazione a ogni singola chiamata RPC, che causa un'attività eccessiva del processo e un sovraccarico ripetuto della configurazione di Binder. Raggruppa invece i burst di lavoro dietro un breve timer di inattività (ad esempio, da 5 a 30 secondi).
Collocazione di un servizio associato con un'interfaccia utente ad alta intensità di memoria
Per impostazione predefinita, tutti i componenti di un APK vengono eseguiti nello stesso processo. Se la tua app
espone un servizio associato leggero (ad esempio un NotificationListenerService, un
provider di widget o un servizio di plug-in a cui il sistema o il launcher si associa) nello
stesso processo del Activity principale, l'intero processo eredita lo
stato elevato del servizio (BFGS o PERC, in genere oom_score_adj di 200 o
inferiore).
Quando l'utente apre la UI della tua app, il processo alloca grandi gerarchie di visualizzazione, bitmap decodificati e buffer grafici. Quando l'utente esce
dall'app, il processo non scende a CACHED (oom_score_adj di 900 o superiore)
perché l'associazione dei servizi attiva mantiene il processo elevato. Ciò causa due
problemi combinati:
- Nessuna compattazione della memoria in background:
CachedAppOptimizerdel sistema compatta i processi solo dopo che entrano nello statoCACHED. - Nessun taglio della memoria LRU memorizzata nella cache o recupero LMK: il sistema non
fornisce callback di taglio in background associati all'elenco LRU memorizzato nella cache
(
TRIM_MEMORY_BACKGROUNDe versioni successive) mentre un'associazione dei servizi mantiene il processo in uno stato elevato. Se la tua app non rilascia esplicitamente le risorse UI suTRIM_MEMORY_UI_HIDDENoActivity.onStop(), le allocazioni UI di picco rimangono bloccate nella RAM con un punteggio OOM privilegiato in cui LMK non può recuperarle facilmente.
Soluzione: elimina esplicitamente le cache dell'interfaccia utente, le bitmap decodificate e i riferimenti alle visualizzazioni quando l'interfaccia utente smette di essere visibile, utilizzando Activity.onStop(), ComponentCallbacks2.onTrimMemory(TRIM_MEMORY_UI_HIDDEN), Application.ActivityLifecycleCallbacks o ProcessLifecycleOwner. In alternativa, sposta il servizio sempre associato in un processo separato e leggero utilizzando l'attributo manifest android:process in modo che il sistema possa spostare il processo dell'UI principale nello stato CACHED per compattare o recuperare la memoria in modo indipendente.
Omissione dei flag di rinuncia alla priorità
La chiamata di bindService() con BIND_AUTO_CREATE trasferisce
la priorità di pianificazione e memoria completa del chiamante al servizio di destinazione. Quando un'app in primo piano
si associa a un servizio di analisi, logging o prefetching in background utilizzando solo
BIND_AUTO_CREATE, promuove involontariamente il worker in background a
BTOP.
Soluzione: quando esegui il binding a servizi ausiliari o best-effort che non richiedono
lo stesso livello di protezione dell'interfaccia utente in primo piano, combina BIND_AUTO_CREATE con
BIND_WAIVE_PRIORITY, BIND_NOT_FOREGROUND o BIND_NOT_PERCEPTIBLE in modo che
il sistema possa comunque gestire il processo di destinazione nell'elenco LRU memorizzato nella cache.
← Località | ↑ Su | A livello di sistema →