Cuando uses Binder para comunicarte entre procesos, ten especial cuidado cuando el proceso remoto esté en estado almacenado en caché o inactivo. Las llamadas a apps almacenadas en caché o inactivas pueden hacer que fallen o consuman recursos de forma innecesaria.
Estados de apps almacenadas en caché e inactivas
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 almacenado en caché. Consulta Procesos y el ciclo de vida de la app para obtener más detalles. Las apps almacenadas en caché se conservan en la memoria en caso de que el usuario vuelva a ellas, pero no se espera que funcionen de forma activa.
Cuando se vincula de un proceso de app a otro, como 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 podría tener conexiones de Binder a clientes, por lo general, en forma de devoluciones de llamada, y, mientras el proceso remoto esté en el estado almacenado en caché, el servidor no se almacenará 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 ingresa al estado almacenado en caché y reanudarlo cuando sale de este estado. Esto evita el trabajo innecesario en los procesos de apps almacenadas en caché.
Para hacer un seguimiento de cuándo las apps ingresan o salen del estado almacenado en caché, usa ActivityManager.addOnUidImportanceListener:
Java
// in ActivityManager or Context
activityManager.addOnUidImportanceListener(
new ActivityManager.OnUidImportanceListener() { ... },
IMPORTANCE_CACHED);
Kotlin
// in ActivityManager or Context
activityManager.addOnUidImportanceListener({ uid, importance ->
// ...
}, IMPORTANCE_CACHED)
Estado inactivo
El sistema puede inactivar una app almacenada en caché para conservar recursos. Cuando una app está inactiva, 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 bloquee de forma indefinida mientras espera que se active el proceso remoto, lo que podría causar inanición de subprocesos o interbloqueos en la app que realiza la 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 activa el proceso remoto. Si se desborda el búfer, el proceso de la app receptora puede fallar. Además, las transacciones almacenadas en búfer podrían quedar obsoletas cuando el proceso de la app se active y las procese.
Para evitar abrumar 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 las apps están inactivas o activas, usa
IBinder.addFrozenStateChangeCallback:
Java
// 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
}
Kotlin
// The binder token of the remote process
val binder: IBinder = service.getBinder()
// Keep track of frozen state
val remoteFrozen = AtomicBoolean(false)
// Update remoteFrozen when the remote process freezes or unfreezes
binder.addFrozenStateChangeCallback(myExecutor) { isFrozen ->
remoteFrozen.set(isFrozen)
}
// When dispatching callbacks to the remote process, pause dispatch if frozen:
if (!remoteFrozen.get()) {
// dispatch callback to remote process
}
C++
Para el código de la plataforma que usa las APIs de Binder de C++:
#include <binder/Binder.h>
#include <binder/IBinder.h>
// The binder token of the remote process
android::sp<android::IBinder> binder = service->getBinder();
// Keep track of frozen state
std::atomic<bool> remoteFrozen = false;
// Define a callback class
class MyFrozenStateCallback : public android::IBinder::FrozenStateChangeCallback {
public:
explicit MyFrozenStateCallback(std::atomic<bool>* frozenState) : mFrozenState(frozenState) {}
void onFrozenStateChanged(bool isFrozen) override {
mFrozenState->store(isFrozen);
}
private:
std::atomic<bool>* mFrozenState;
};
// Update remoteFrozen when the remote process freezes or unfreezes
if (binder != nullptr) {
binder->addFrozenStateChangeCallback(android::sp<android::IBinder::FrozenStateChangeCallback>::make(
new MyFrozenStateCallback(&remoteFrozen)));
}
// When dispatching callbacks to the remote process, pause dispatch if frozen:
if (!remoteFrozen.load()) {
// dispatch callback to remote process
}
Usa RemoteCallbackList
La clase RemoteCallbackList es una ayuda para administrar listas de
IInterface devoluciones de llamada que registran los procesos remotos. Esta clase controla automáticamente las notificaciones de finalización de Binder y proporciona opciones para controlar las devoluciones de llamada a apps inactivas.
Cuando compilas RemoteCallbackList, puedes especificar una política de destinatario inactivo:
FROZEN_CALLEE_POLICY_DROP: Las devoluciones de llamada a apps inactivas se descartan de forma silenciosa. Usa esta política cuando los eventos que ocurrieron mientras la app estaba almacenada en caché no sean importantes para la app, por ejemplo, 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 se activa la app. 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 contenido multimedia actual.FROZEN_CALLEE_POLICY_ENQUEUE_ALL: Todas las devoluciones de llamada transmitidas mientras una app está inactiva se ponen en cola y se entregan cuando se activa la app. 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 inactivas:
Java
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));
Kotlin
val callbacks =
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 apps
Los servicios del sistema suelen interactuar con muchas apps diferentes mediante Binder. Debido a que las apps pueden ingresar a estados almacenados en caché e inactivos, los servicios del sistema deben tener especial cuidado para controlar estas interacciones de manera fluida para ayudar a mantener la estabilidad y el rendimiento del sistema.
Los servicios del sistema ya deben controlar situaciones en las que los procesos de apps se finalizan por varios motivos. Esto implica detener el trabajo en su nombre y no intentar seguir entregando devoluciones de llamada a procesos finalizados. La consideración de que las apps estén inactivas es una extensión de esta responsabilidad de supervisión existente.
Haz un seguimiento de los estados de las apps 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 apps:
ActivityManager.addOnUidImportanceListener: Los servicios del sistema pueden registrar un objeto de escucha para hacer un seguimiento de los cambios de 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 está en estado almacenado en caché.IBinder.addFrozenStateChangeCallback: Cuando un servicio del sistema recibe un objeto de Binder de una app (por ejemplo, como parte de un registro para devoluciones de llamada), debe registrar unFrozenStateChangeCallbacken esa instancia específica deIBinder. Esto notifica directamente al servicio del sistema cuando el proceso de la app que aloja ese Binder se activa o desactiva.
Recomendaciones para los servicios del sistema
Recomendamos que todos los servicios del sistema que puedan interactuar con las apps hagan un seguimiento del estado almacenado en caché e inactivo de los procesos de apps con los que se comunican. Si no lo haces, puede ocurrir lo siguiente:
- Consumo de recursos: Realizar tareas para apps almacenadas en caché y no visibles para el usuario puede desperdiciar recursos del sistema.
- Fallas de apps: Las llamadas de Binder síncronas a apps inactivas hacen que fallen. Las llamadas de Binder asíncronas a apps inactivas provocan una falla si se desborda su búfer de transacciones asíncronas.
- Comportamiento inesperado de la app: Las apps activas reciben de inmediato las transacciones de Binder asíncronas almacenadas en búfer que se les enviaron mientras estaban inactivas. Las apps pueden pasar un período indefinido en el congelador, por lo que las transacciones almacenadas en búfer pueden estar muy obsoletas.
Los servicios del sistema suelen usar RemoteCallbackList para administrar devoluciones de llamada remotas
y controlar automáticamente los procesos finalizados. Para controlar las apps inactivas, extiende el
uso existente de RemoteCallbackList aplicando una política de destinatario inactivo
como se describe en Usa RemoteCallbackList.