When using binder to communicate between processes, take special care when the remote process is in a cached or frozen state. Calls into cached or frozen apps can cause them to crash or consume resources unnecessarily.
Cached and frozen app states
Android maintains apps in different states to manage system resources like memory and CPU.
Cached state
When an app has no user-visible components, such as activities or services, it can be moved to the cached state. See Processes and app lifecycle for details. Cached apps are kept in memory in case the user switches back to them, but they aren't expected to be actively working.
When binding from one app process to another, such as using bindService,
the process state of the server process is elevated to be at least as important
as the client process (except when Context#BIND_WAIVE_PRIORITY is
specified). For example, if the client isn't in the cached state, neither is the
server.
Conversely, the state of a server process doesn't determine that of its clients. So a server might have binder connections to clients, most commonly in the form of callbacks, and while the remote process is in the cached state, the server isn't cached.
When designing APIs where callbacks originate in an elevated process and are delivered to apps, consider pausing dispatch of callbacks when an app enters the cached state, and resuming when it exits this state. This prevents unnecessary work in cached app processes.
To track when apps enter or exit the cached state, use
ActivityManager.addOnUidImportanceListener:
// in ActivityManager or Context
activityManager.addOnUidImportanceListener(
new UidImportanceListener() { ... },
IMPORTANCE_CACHED);
Frozen state
The system can freeze a cached app to conserve resources. When an app is frozen, it receives zero CPU time and can't do any work. For more details, see Cached apps freezer.
When a process sends a synchronous (not oneway) binder transaction to another
remote process that is frozen, the system kills the remote process. This
prevents the calling thread in the calling process from hanging indefinitely
while waiting for the remote process to be unfrozen, which could cause thread
starvation or deadlocks in the calling app.
When a process sends an asynchronous (oneway) binder transaction to a frozen
app (commonly by notifying a callback, which is typically a oneway method),
the transaction is buffered until the remote process is unfrozen. If the buffer
overflows, the recipient app process can crash. Additionally, buffered
transactions might become stale by the time the app process is unfrozen and
processes them.
To avoid overwhelming apps with stale events or overflowing their buffers, you must pause dispatching callbacks while the recipient app's process is frozen.
To track when apps are frozen or unfrozen, use
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
}
Use RemoteCallbackList
The RemoteCallbackList class is a helper for managing lists of
IInterface callbacks that remote processes register. This class automatically
handles binder death notifications and provides options for handling callbacks
to frozen apps.
When building RemoteCallbackList, you can specify a frozen callee policy:
FROZEN_CALLEE_POLICY_DROP: Callbacks to frozen apps are silently dropped. Use this policy when events that happened while the app was cached aren't important to the app, for example, real-time sensor events.FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT: If multiple callbacks are broadcast while an app is frozen, only the most recent one is enqueued and delivered when the app is unfrozen. This is useful for state-based callbacks where only the most recent state update matters, for example, a callback notifying the app of the current media volume.FROZEN_CALLEE_POLICY_ENQUEUE_ALL: All callbacks broadcast while an app is frozen are enqueued and delivered when the app is unfrozen. Be cautious with this policy, because it can lead to buffer overflows if too many callbacks are enqueued, or to the accumulation of stale events.
The following example shows how to create and use a RemoteCallbackList
instance that drops callbacks to frozen apps:
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));
If you use FROZEN_CALLEE_POLICY_DROP, the system invokes
callback.onSomeEvent() only if the process hosting the callback isn't frozen.
System services and app interactions
System services often interact with many different apps using binder. Because apps can enter cached and frozen states, system services must take special care to handle these interactions gracefully to help maintain system stability and performance.
System services already need to handle situations where app processes are killed for various reasons. This involves stopping work on their behalf and not attempting to continue delivering callbacks to dead processes. The consideration of apps being frozen is an extension of this existing monitoring responsibility.
Track app states from system services
System services, running in system_server or as native daemons, can also use
the APIs described earlier to track the importance and frozen state of app
processes:
ActivityManager.addOnUidImportanceListener: System services can register a listener to track UID importance changes. When receiving a binder call or callback from an app, the service can useBinder.getCallingUid()to get the UID and correlate it with the importance state tracked by the listener. This lets system services know if the calling app is in a cached state.IBinder.addFrozenStateChangeCallback: When a system service receives a binder object from an app (for example, as part of a registration for callbacks), it should register aFrozenStateChangeCallbackon that specificIBinderinstance. This directly notifies the system service when the app process hosting that binder becomes frozen or unfrozen.
Recommendations for system services
We recommend that all system services that might interact with apps track the cached and frozen state of the app processes they communicate with. Failing to do so can lead to:
- Resource consumption: Performing work for apps that are cached and not user-visible can waste system resources.
- App crashes: Synchronous binder calls to frozen apps crash them. Asynchronous binder calls to frozen apps lead to a crash if their async transaction buffer overflows.
- Unexpected app behavior: Unfrozen apps wi immediately receive any buffered asynchronous binder transactions that were sent to them while they were frozen. Apps can spend an indefinite period in the freezer, so buffered transactions can be very stale.
System services often use RemoteCallbackList to manage remote callbacks
and automatically handle dead processes. To handle frozen apps, extend the
existing usage of RemoteCallbackList by applying a frozen callee policy
as described in Use RemoteCallbackList.