AOSP Framework & Internals
3 min read

Process Boundaries and Proxies

Learn about Process Boundaries and Proxies.

Android's component model (Activities, Services) relies on the illusion that components can seamlessly invoke methods on each other. However, these components often run in completely separate Linux processes. To maintain this illusion, Android utilizes the Proxy pattern built on top of Binder.

IBinder Interface

IBinder is the base interface for all cross-process communication in Android. It defines the core transact() method, which sends a synchronous or asynchronous message across the process boundary.

Every object that can be passed across a Binder transaction must implement IBinder.

BpBinder (Proxy) and BBinder (Stub)

The Binder architecture is strictly divided into two halves:

  1. BpBinder (Binder Proxy): This lives in the client process. To the client, this object looks exactly like the local service. However, calling a method on the proxy does not execute the logic locally. Instead, the proxy packs the method arguments into a Parcel and calls transact(), pushing the data to the kernel.
  2. BBinder (Binder Stub): This lives in the server process. It sits in a thread pool, waiting for incoming transactions from the kernel. When a transaction arrives, BBinder unpacks the Parcel, reads the method ID, and invokes the actual concrete implementation of the method.

The AIDL (Android Interface Definition Language) compiler automatically generates these Proxy and Stub classes for you.

Parcel: Data Serialization for Binder

You cannot pass a raw memory pointer from one process to another, as virtual memory addresses are meaningless across process boundaries. Data must be serialized.

In Android, this is handled by the Parcel class. Parcel is a highly optimized container for data and object references. Unlike Java's standard Serializable, Parcel is designed purely for high-speed, ephemeral IPC, not for saving to disk.

When you pass a primitive (like an int), Parcel copies it directly into a byte buffer. When you pass a Parcelable object, it calls the object's writeToParcel() method.

Crucially, Parcel can also serialize IBinder objects. If Process A passes an IBinder to Process B, the kernel intercepts the transaction, translates Process A's handle to the object into a valid handle for Process B, and updates its internal reference counts.

Binder Handles and Object References

Within the kernel driver, a Binder object is represented by a binder_node struct.

When Process A gets a reference to an object in Process B, Process A doesn't get a memory address. It gets a handle (a 32-bit integer).

The kernel maintains a per-process mapping table. For Process A, Handle 1 might point to binder_node_X. For Process C, Handle 5 might point to that exact same binder_node_X.

When Process A calls transact(1, data), the kernel looks up Handle 1, finds the destination process (Process B), wakes up a thread in Process B, and passes the transaction along. This handle abstraction ensures that processes cannot spoof or hijack objects belonging to other processes.