Cuando uses el binder para comunicarte entre procesos, ten especial cuidado cuando el proceso remoto esté en un estado almacenado en caché o inactivo. Las llamadas a apps almacenadas en caché o inmovilizadas pueden provocar que se fallen o consuman recursos de forma innecesaria.
Estados de la app almacenados en caché y congelados
Android mantiene las apps en diferentes estados para administrar los recursos del sistema, como la memoria y la CPU.
Estado almacenado en caché
Cuando una app no tiene componentes visibles para el usuario, como actividades o servicios, se puede mover al estado en caché. Consulta Procesos y el ciclo de vida de la app para obtener más detalles. Las apps almacenadas en caché se mantienen en la memoria en caso de que el usuario vuelva a ellas, pero no se espera que funcionen de forma activa.
Cuando se vincula un proceso de una app a otro, por ejemplo, cuando se usa bindService, el estado del proceso del servidor se eleva para que sea al menos tan importante como el proceso del cliente (excepto cuando se especifica Context#BIND_WAIVE_PRIORITY). Por ejemplo, si el cliente no está en el estado almacenado en caché, tampoco lo está el servidor.
Por el contrario, el estado de un proceso del servidor no determina el de sus clientes. Por lo tanto, un servidor puede tener conexiones de Binder con clientes, por lo general, en forma de devoluciones de llamada, y, si bien el proceso remoto se encuentra en el estado almacenado en caché, el servidor no se almacena en caché.
Cuando diseñes APIs en las que las devoluciones de llamada se originan en un proceso elevado y se entregan a las apps, considera pausar el envío de devoluciones de llamada cuando una app entra en el estado almacenado en caché y reanudarlo cuando sale de este estado. Esto evita el trabajo innecesario en los procesos de la app almacenados en caché.
Para hacer un seguimiento de cuándo las apps entran o salen del estado almacenado en caché, usa ActivityManager.addOnUidImportanceListener:
// in ActivityManager or Context
activityManager.addOnUidImportanceListener(
new UidImportanceListener() { ... },
IMPORTANCE_CACHED);
Estado de congelación
El sistema puede inmovilizar una app almacenada en caché para conservar recursos. Cuando una app se inmoviliza, no recibe tiempo de CPU y no puede realizar ninguna tarea. Para obtener más detalles, consulta Congelador de apps almacenadas en caché.
Cuando un proceso envía una transacción de Binder síncrona (no oneway) a otro proceso remoto que está inactivo, el sistema finaliza el proceso remoto. Esto evita que el subproceso de llamada en el proceso de llamada se detenga de forma indefinida mientras espera que se descongele el proceso remoto, lo que podría causar inanición de subprocesos o interbloqueos en la app de llamada.
Cuando un proceso envía una transacción de Binder asíncrona (oneway) a una app inactiva (por lo general, notificando una devolución de llamada, que suele ser un método oneway), la transacción se almacena en búfer hasta que se reactiva el proceso remoto. Si el búfer se desborda, el proceso de la app receptora puede fallar. Además, es posible que las transacciones almacenadas en búfer se vuelvan obsoletas cuando se descongele el proceso de la app y se procesen.
Para evitar sobrecargar las apps con eventos obsoletos o desbordar sus búferes, debes pausar el envío de devoluciones de llamada mientras el proceso de la app receptora esté inactivo.
Para hacer un seguimiento de cuándo se congelan o descongelan las apps, usa 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
}
Cómo usar RemoteCallbackList
La clase RemoteCallbackList es una clase de ayuda para administrar listas de devoluciones de llamada IInterface que registran los procesos remotos. Esta clase controla automáticamente las notificaciones de muerte del binder y proporciona opciones para controlar las devoluciones de llamada a las apps congeladas.
Cuando compilas RemoteCallbackList, puedes especificar una política de receptor congelada:
FROZEN_CALLEE_POLICY_DROP: Las devoluciones de llamada a apps congeladas se descartan de forma silenciosa. Usa esta política cuando los eventos que ocurrieron mientras la app estaba en caché no sean importantes para ella, por ejemplo, los eventos de sensores en tiempo real.FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT: Si se transmiten varias devoluciones de llamada mientras una app está inactiva, solo se pone en cola y se entrega la más reciente cuando la app se activa. Esto es útil para las devoluciones de llamada basadas en el estado, en las que solo importa la actualización de estado más reciente, por ejemplo, una devolución de llamada que notifica a la app el volumen de medios actual.FROZEN_CALLEE_POLICY_ENQUEUE_ALL: Todas las devoluciones de llamada que se transmiten mientras una app está inactiva se ponen en cola y se entregan cuando la app se activa. Ten cuidado con esta política, ya que puede provocar desbordamientos de búfer si se ponen en cola demasiadas devoluciones de llamada o la acumulación de eventos obsoletos.
En el siguiente ejemplo, se muestra cómo crear y usar una instancia de RemoteCallbackList que descarta las devoluciones de llamada a apps inmovilizadas:
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));
Si usas FROZEN_CALLEE_POLICY_DROP, el sistema invoca callback.onSomeEvent() solo si el proceso que aloja la devolución de llamada no está inactivo.
Servicios del sistema e interacciones de la app
Los servicios del sistema suelen interactuar con muchas apps diferentes a través de Binder. Dado que las apps pueden entrar en estados de caché y congelados, los servicios del sistema deben tener especial cuidado para controlar estas interacciones de manera fluida y ayudar a mantener la estabilidad y el rendimiento del sistema.
Los servicios del sistema ya deben controlar situaciones en las que se detienen los procesos de las apps por varios motivos. Esto implica detener el trabajo en su nombre y no intentar seguir entregando devoluciones de llamada a procesos inactivos. La consideración de las apps que se congelan es una extensión de esta responsabilidad de supervisión existente.
Realiza un seguimiento de los estados de la app desde los servicios del sistema
Los servicios del sistema, que se ejecutan en system_server o como daemons nativos, también pueden usar las APIs descritas anteriormente para hacer un seguimiento de la importancia y el estado inactivo de los procesos de la app:
ActivityManager.addOnUidImportanceListener: Los servicios del sistema pueden registrar un objeto de escucha para hacer un seguimiento de los cambios en la importancia del UID. Cuando recibe una llamada o devolución de llamada de Binder desde una app, el servicio puede usarBinder.getCallingUid()para obtener el UID y correlacionarlo con el estado de importancia que rastrea el objeto de escucha. Esto permite que los servicios del sistema sepan si la app que realiza la llamada se encuentra en un estado almacenado en caché.IBinder.addFrozenStateChangeCallback: Cuando un servicio del sistema recibe un objeto Binder de una app (por ejemplo, como parte de un registro para devoluciones de llamada), debe registrar unFrozenStateChangeCallbacken esa instancia deIBinderespecífica. Esto notifica directamente al servicio del sistema cuando el proceso de la app que aloja ese vinculador se congela o se descongela.
Recomendaciones para los servicios del sistema
Recomendamos que todos los servicios del sistema que puedan interactuar con las apps realicen un seguimiento del estado almacenado en caché y congelado de los procesos de las apps con los que se comunican. De lo contrario, se pueden producir los siguientes problemas:
- Consumo de recursos: Realizar trabajo para apps que están en caché y no son visibles para el usuario puede desperdiciar recursos del sistema.
- Fallas de la app: Las llamadas del vinculador síncronas a apps congeladas provocan fallas en ellas. Las llamadas asíncronas del vinculador a apps congeladas provocan una falla si se desborda su búfer de transacciones asíncronas.
- Comportamiento inesperado de la app: Las apps descongeladas recibirán de inmediato las transacciones asíncronas de Binder almacenadas en búfer que se les enviaron mientras estaban congeladas. Las apps pueden pasar un período indefinido en el congelador, por lo que las transacciones almacenadas en búfer pueden estar muy desactualizadas.
Los servicios del sistema suelen usar RemoteCallbackList para administrar devoluciones de llamada remotas y controlar automáticamente los procesos inactivos. Para controlar las apps congeladas, extiende el uso existente de RemoteCallbackList aplicando una política de receptor congelado, como se describe en Usa RemoteCallbackList.