Quando utilizzi Binder per comunicare tra processi, presta particolare attenzione quando il processo remoto è in uno stato memorizzato nella cache o bloccato. Le chiamate in app memorizzate nella cache o bloccate possono causare arresti anomali o consumare risorse inutilmente.
Stati delle app memorizzati nella cache e bloccati
Android mantiene le app in stati diversi per gestire le risorse di sistema come memoria e CPU.
Stato memorizzato nella cache
Quando un'app non ha componenti visibili all'utente, come attività o servizi, può essere spostata allo stato memorizzato nella cache. Per maggiori dettagli, consulta Processi e ciclo di vita dell'app. Le app memorizzate nella cache vengono mantenute in memoria nel caso in cui l'utente torni a utilizzarle, ma non è previsto che funzionino attivamente.
Quando si esegue il binding da un processo dell'app a un altro, ad esempio utilizzando bindService,
lo stato del processo del server viene elevato in modo che sia almeno importante
quanto il processo client (tranne quando viene specificato Context#BIND_WAIVE_PRIORITY). Ad esempio, se il client non è nello stato memorizzato nella cache, non lo è nemmeno il
server.
Al contrario, lo stato di un processo del server non determina quello dei relativi client. Pertanto, un server potrebbe avere connessioni binder ai client, più comunemente sotto forma di callback, e mentre il processo remoto è nello stato memorizzato nella cache, il server non viene memorizzato nella cache.
Quando progetti API in cui i callback hanno origine in un processo con privilegi elevati e vengono distribuiti alle app, valuta la possibilità di sospendere l'invio dei callback quando un'app entra nello stato memorizzato nella cache e di riprenderlo quando esce da questo stato. In questo modo si evita l'esecuzione di operazioni non necessarie nei processi delle app memorizzati nella cache.
Per monitorare quando le app entrano o escono dallo stato memorizzato nella cache, utilizza
ActivityManager.addOnUidImportanceListener:
// in ActivityManager or Context
activityManager.addOnUidImportanceListener(
new UidImportanceListener() { ... },
IMPORTANCE_CACHED);
Stato bloccato
Il sistema può congelare un'app memorizzata nella cache per risparmiare risorse. Quando un'app è congelata, riceve zero tempo CPU e non può svolgere alcuna attività. Per maggiori dettagli, vedi Congelamento delle app memorizzate nella cache.
Quando un processo invia una transazione binder sincrona (non oneway) a un altro processo remoto bloccato, il sistema termina il processo remoto. In questo modo
si impedisce al thread di chiamata nel processo di chiamata di bloccarsi indefinitamente
durante l'attesa che il processo remoto venga scongelato, il che potrebbe causare
la carenza di thread o deadlock nell'app di chiamata.
Quando un processo invia una transazione binder asincrona (oneway) a un'app bloccata (in genere notificando un callback, che in genere è un metodo oneway),
la transazione viene memorizzata nel buffer finché il processo remoto non viene sbloccato. Se il buffer
si riempie, il processo dell'app destinataria può arrestarsi in modo anomalo. Inoltre, le transazioni
memorizzate nel buffer potrebbero diventare obsolete nel momento in cui il processo dell'app viene riattivato e
le elabora.
Per evitare di sovraccaricare le app con eventi obsoleti o di riempire i buffer, devi mettere in pausa l'invio dei callback mentre il processo dell'app destinataria è bloccato.
Per monitorare quando le app vengono bloccate o sbloccate, utilizza
IBinder.addFrozenStateChangeCallback:
// The binder token of the remote process
IBinder binder = service.getBinder();
// Keep track of frozen state
AtomicBoolean remoteFrozen = new AtomicBoolean(false);
// Update remoteFrozen when the remote process freezes or unfreezes
binder.addFrozenStateChangeCallback(
myExecutor,
new IBinder.FrozenStateChangeCallback() {
@Override
public void onFrozenStateChanged(boolean isFrozen) {
remoteFrozen.set(isFrozen);
}
});
// When dispatching callbacks to the remote process, pause dispatch if frozen:
if (!remoteFrozen.get()) {
// dispatch callback to remote process
}
Utilizzare RemoteCallbackList
La classe RemoteCallbackList è un helper per la gestione degli elenchi di
callback IInterface registrati dai processi remoti. Questa classe gestisce automaticamente
le notifiche di interruzione del binder e fornisce opzioni per la gestione dei callback
alle app bloccate.
Quando crei RemoteCallbackList, puoi specificare un criterio del chiamante bloccato:
FROZEN_CALLEE_POLICY_DROP: i callback alle app bloccate vengono eliminati automaticamente. Utilizza questo criterio quando gli eventi che si sono verificati mentre l'app era memorizzata nella cache non sono importanti per l'app, ad esempio gli eventi dei sensori in tempo reale.FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT: se vengono trasmesse più callback mentre un'app è bloccata, viene accodato e recapitato solo quello più recente quando l'app viene sbloccata. Questo è utile per i callback basati sullo stato in cui conta solo l'aggiornamento dello stato più recente, ad esempio un callback che notifica all'app il volume multimediale attuale.FROZEN_CALLEE_POLICY_ENQUEUE_ALL: Tutti i callback trasmessi mentre un'app è congelata vengono accodati e inviati quando l'app viene scongelata. Fai attenzione con queste norme, perché possono causare overflow del buffer se vengono accodate troppe callback o all'accumulo di eventi obsoleti.
L'esempio seguente mostra come creare e utilizzare un'istanza RemoteCallbackList
che elimina i callback alle app bloccate:
RemoteCallbackList<IMyCallbackInterface> callbacks =
new RemoteCallbackList.Builder<IMyCallbackInterface>(
RemoteCallbackList.FROZEN_CALLEE_POLICY_DROP)
.setExecutor(myExecutor)
.build();
// Registering a callback:
callbacks.register(callback);
// Broadcasting to all registered callbacks:
callbacks.broadcast((callback) -> callback.onSomeEvent(eventData));
Se utilizzi FROZEN_CALLEE_POLICY_DROP, il sistema richiama
callback.onSomeEvent() solo se il processo che ospita il callback non è bloccato.
Servizi di sistema e interazioni con le app
I servizi di sistema spesso interagiscono con molte app diverse utilizzando Binder. Poiché le app possono entrare in stati di cache e blocco, i servizi di sistema devono prestare particolare attenzione per gestire queste interazioni in modo appropriato al fine di contribuire a mantenere la stabilità e le prestazioni del sistema.
I servizi di sistema devono già gestire situazioni in cui i processi delle app vengono interrotti per vari motivi. Ciò comporta l'interruzione del lavoro per loro conto e il mancato tentativo di continuare a inviare callback a processi non attivi. La valutazione del blocco delle app è un'estensione di questa responsabilità di monitoraggio esistente.
Monitorare gli stati delle app dai servizi di sistema
I servizi di sistema, in esecuzione in system_server o come daemon nativi, possono anche utilizzare
le API descritte in precedenza per monitorare l'importanza e lo stato di blocco dei processi
delle app:
ActivityManager.addOnUidImportanceListener: i servizi di sistema possono registrare un listener per monitorare le modifiche all'importanza dell'UID. Quando riceve una chiamata o un callback di binder da un'app, il servizio può utilizzareBinder.getCallingUid()per ottenere l'UID e metterlo in correlazione con lo stato di importanza monitorato dal listener. In questo modo, i servizi di sistema sanno se l'app di chiamata è in uno stato memorizzato nella cache.IBinder.addFrozenStateChangeCallback: quando un servizio di sistema riceve un oggetto binder da un'app (ad esempio, nell'ambito di una registrazione per i callback), deve registrare unFrozenStateChangeCallbacksu quella specifica istanza diIBinder. In questo modo, il servizio di sistema riceve una notifica diretta quando il processo dell'app che ospita il binder viene bloccato o sbloccato.
Consigli per i servizi di sistema
Ti consigliamo di fare in modo che tutti i servizi di sistema che potrebbero interagire con le app tengano traccia dello stato memorizzato nella cache e bloccato dei processi delle app con cui comunicano. In caso contrario, potresti rischiare:
- Consumo di risorse:l'esecuzione di operazioni per app memorizzate nella cache e non visibili all'utente può sprecare risorse di sistema.
- Arresti anomali delle app:le chiamate sincrone del binder alle app bloccate causano l'arresto anomalo. Le chiamate asincrone del binder alle app bloccate causano un arresto anomalo se il buffer delle transazioni asincrone overflow.
- Comportamento imprevisto dell'app:le app scongelate riceveranno immediatamente tutte le transazioni binder asincrone memorizzate nel buffer che sono state inviate mentre erano congelate. Le app possono rimanere nel freezer per un periodo di tempo indefinito, quindi le transazioni memorizzate possono essere molto obsolete.
I servizi di sistema spesso utilizzano RemoteCallbackList per gestire i callback remoti e gestire automaticamente i processi non attivi. Per gestire le app bloccate, estendi l'utilizzo esistente di RemoteCallbackList applicando un criterio di chiamata bloccata come descritto in Utilizzare RemoteCallbackList.