Binder Architecture and Core Components
Binder components
Native and proxy Binders
Binder framework takes an object-oriented approach in its design principle and every entity that is capable of serving inter-process calls is a user-space object. Let’s use our HamKing project [1] as an example: The server side app creates a RemoteService
object which provides the actual implementation of the IHamKingInterface
Binder service interface. The client side app creates a handle mService
referring to this remote object via the auto-generated method IHamKingInterface.Stub.asInterface
. The client side app can then use this handle to make a remote Binder call to the server side app.
We refer to a Binder object as “local” or “native” if the object is created in the current process. Some articles and documentations refer to this kind of object as Bn
object which is short for "Binder native". In contrast, we refer a Binder object as "remote" if the object is created in another process. The handle we use to call into a remote Binder object is called a "proxy". Some articles and documentations refer to this kind of object as Bp
object which is short for "Binder proxy". The terms "proxy" comes from the fact that this kind of objects just delegate the calls to Bn
side. As you can see Binder is a typical client-server IPC mechanism. But note that whenever we talk about client or server, the subject is always a Binder object, not a process. A process can create a bunch of Bn
objects and Bp
objects at the same time so there is nothing called a server or client process in Binder. In the example above, the RemoteService
instance in server app is a native Binder object because it is the object that implements the Binder interface. The mService
field retrieved in onServiceConnected
in client app is a proxy Binder object because it just references the RemoteService
instance in server app.
Binder kernel driver
Most IPC mechanisms require kernel support and Binder is no exception. The reason is that modern processors work in protected mode under which each process is given an isolated virtual memory space. Virtual memory address is the address that appears in a program and it will be translated to physical address before addressing main memory. How exactly such virtual to physical mapping is wired up is written in a kind of memory data structure called page table. Each process has a different root page entry so each process has an independent virtual to physical address wiring, thus an independent virtual memory space.
Even though each process has a separate virtual memory space, the Linux kernel makes sure that every process has the same virtual to physical address mapping in kernel space. This means each process has a shared kernel virtual memory space. For example on i386
architecture the virtual memory address between 3 GB to 4 GB is used by the kernel and each process' page table has the same mapping between this range. So in kernel mode one process can access another process's data structures. So there needs to be a Binder driver to act as a bridge between processes. Binder driver copies over data from one process's memory space to another.
The Binder kernel driver creates a device file /dev/binder
while initializing. Binder driver exposes this interface so that native user space programs can use its services. A user space program mainly interacts with Binder driver using three system calls: open
, mmap
and ioctl
. A process calls open
to register itself as a user of Binder driver. mmap
is used to create a kernel data buffer and reserve a range of user space virtual address for it. After open
and mmap
a process can interact with the driver using ioctl
. The most important ioctl
command is BINDER_WRITE_READ
with which a process can write and read data payload from the Binder driver. A typical flow that a user space program interacts with Binder driver is as follows:
I mentioned earlier that Binder driver creates a device file /dev/binder
. Well that statement is not entirely true from Android Oreo release, during which the project Treble was implemented. From then on the /dev/binder
should only be used by Android framework and two more device files /dev/vndbinder
and /dev/hwbinder
are created for vendor processes. What's more, the latest Binder driver introduced binderfs
mechanism to better support Android running in containers. However, these device files are backed by the same kernel driver code base so we will only analyze Android framework's interaction with /dev/binder
in this series.
Service Manager
We mentioned in the article “Binder introduction” that during the process of launching an Activity
, a process interacts with several key system services like ActivityTaskManagerService
and WindowManagerService
. These system services run in other processes so Binder mechanism is used to access them. The Service Manager manages all these services by maintaining a mapping between a service name to a Bp
handle of the service. When a process requests for a service by name, Service Manager will look it up in the mapping and return it to the requester if found. You can view the list registered services via service list
command inside adb shell
.
Each service that Service Manager keeps a record of is a Binder object that’s created by another system process. Each of these well known Binder objects have a readable name attached to it. This kind of named Binder objects only compose a small fraction of the total number of Binder objects created in Android system.
To sum up, the key components in a Binder system are Binder native objects, Binder proxy objects, Service Manager and Binder kernel driver. Next we will take a deeper look at these components.
Binder driver first look
In this section we will go through the source code of Binder kernel driver. We will take a step by step look at Binder driver initialization and how it handles open
and mmap
system calls. The ioctl
system call is too complicated to fully walk through in this section so we will only take a brief look at it.
Binder driver initialization
binder_init
will be invoked during system booting. Line 38 checks whether CONFIG_ANDROID_BINDERFS
is set to false and registers configured Binder device files. If the kernel is not intended to run in a containerized environment then CONFIG_ANDROID_BINDERFS
is likely to be disabled. The binder_names
can be configured but by default it is binder,hwbinder,vndbinder
which indicates that three binder device files should be created: /dev/binder
, /dev/hwbinder
and /dev/vndbinder
. init_binder_device
creates one instance of device file and binds binder_fops
to it. As we can see, when user space program calls open
, mmap
and ioctl
then binder_open
, binder_mmap
and binder_ioctl
will be invoked to serve the corresponding system calls.
binder_open walk through
The main task of binder_open
is to create an instance of binder_proc
as a book keeping structure for the calling process. Every process that is interacting with Binder has one instance of binder_proc
in Binder driver. Line 8 allocates a binder_proc
and the following lines do some initialization on it. Line 23 binds it to the current file pointer's private data so that later system calls can retrieve this binder_proc
instance. Line 25 adds this binder_proc
instance to a global hash table binder_proc
structures.
The binder_proc
is an important data structure in Binder driver. It does represents a user space process who is interacting with Binder driver.
proc_node
is used to link this binder_proc
structure into a hash table. A process contains a bunch of threads that are interacting with Binder driver and the threads
field is a red black tree of binder_thread
structures representing these threads.
Each local Binder object has a corresponding binder_node
structure in Binder driver and nodes
is a tree of such native Binder objects; for example, the RemoteService
instance in the server app of HamKing has a corresponding binder_node
in this tree. refs_by_desc
and refs_by_node
are two trees of binder_ref
structures representing all remote Binder objects that the current process is referencing. For example, the client app in HamKing project references the RemoteService
instance in server app, so Binder driver keeps a binder_ref
structure in the two trees inside the binder_proc
structure of client app process. When a process starts referencing a remote Binder object, a binder_ref
structure will be created and inserted into the two trees. The two trees contain the same set of binder_ref
structures but use different keys to sort them.
pid
is the process identifier number of the corresponding process and tsk
is the kernel process managing structure of this process.
todo
is the list of work items for the process. Binder uses binder_work
structure to represent a work item and the todo
field is a list of binder_work
s. For example, when a process wants to invoke a service method in a remote Binder object, a binder_work
of type BINDER_WORK_TRANSACTION
will be enqueued into the target binder_proc
's todo
list in most cases.
max_threads
is the maximum number of passive Binder threads allowed in the corresponding process. It is set by user space via ioctl
system call. Binder driver can request user space to spawn new Binder threads when it cannot find an idle Binder thread to schedule work on, requested_threads
and requested_threads_started
keeps track of the number of those requests. waiting_threads
is the list of idle Binder threads that are waiting for work to do. Passive Binder threads are those requested by Binder driver, however, user space threads can join Binder thread pool proactively and Binder driver has no limitation on how many threads can proactively join the thread pool.
binder_alloc
structure manages the process's kernel Binder buffer and each binder_proc
structure has exactly one binder_alloc
embedded.
context
points to a context manager object of current Binder device instance. We just talked about Service Manager and in Binder kernel such an entity is called "context manager". Each Binder device instance can only have one context manager registered and only a privileged process can do the registration. When the Service Manager in Android framework starts, it will register itself as the context manager of /dev/binder
device. Each binder_proc
created under the /dev/binder
device will have a pointer context
pointing to that context manager.
binder_mmap walk through
The vm_area_struct
structure is what Linux uses to track the allocated user space virtual memory of a process. At the time binder_mmap
is called the kernel has already found a free range of virtual memory satisfying the user space mmap
request. The actual work is done in binder_alloc_mmap_handler
function.
Before we go into binder_alloc_mmap_handler
let's take a look at the binder_alloc
and binder_buffer
structures. Each process has exactly one instance of binder_alloc
associated with. The buffer
field is set the virtual memory address that user space gets by calling mmap
and vma
manages this range of virtual memory. When user space performs one transaction with the kernel it only needs a small fraction of total allocated buffer so the driver uses binder_buffer
to manage those fractions. buffers
field is a list of all binder_buffer
s. free_buffers
and allocated_buffers
are two trees containing all free and allocated buffers respectively. Initially there is only one binder_buffer
representing the whole virtual memory range allocated by mmap
. But as Binder transactions happen the binder_buffer
will be fragmented due to consistent cutting, merging, freeing and allocating of buffers. free_async_space
is the size of buffer available for asynchronous Binder transactions. Asynchronous transaction mean the source process would not wait for target process to reply. This kind of Binder transactions has lower priority to schedule and run. binder_lru_page
structure manages a physical page frame that Binder driver is using and pages
is an array of such structures. A typical page frame size is 4 KB so if user space requests say 1 MB of data buffer then the array size will be 256.
Let’s take a look at binder_buffer
structure. entry
and rb_node
are used to add the structure to the linked list (buffers
field in binder_alloc
) or red black tree (free_buffers
or allocated_buffers
field in binder_alloc
) inside binder_alloc
. free
indicates whether the binder_buffer
is free or allocated. allow_user_free
tells whether user space can use BC_FREE_BUFFER
command to free the buffer. When a buffer is no longer used by the kernel, then this field will be true. async_transaction
indicates whether the buffer is used for asynchronous Binder transactions.
A buffer will be allocated only during a Binder transaction and the transaction
field points to the Binder transaction instance; besides, target_node
points to the target Binder object of the transaction. For example, in our HamKing sample project, when the client app calls requestOrder
service method in the server Binder object, a binder_transaction
instance will be created for this call and a binder_buffer
will be created to facilitate this transaction and the target_node
will point to the binder_node
representing the RemoteService
instance.
user_data
points to the starting virtual address of this buffer. During a transaction the user space data are serialized and packed into the address pointed to by user_data
. There are mainly two types of data packed into a data buffer: primitive data types and live objects. Primitive data types include plain integers, strings etc, they are just copied over to target process as is. Live objects mainly include native Binders, proxy Binders and file descriptors. It shouldn't be a surprise that Binder driver needs to do some special handling for these kind of live objects. But the problem is that all primitive data and live objects are packed into the buffer pointed to user_data
, so the driver needs to know the offsets of these live objects in the buffer, otherwise there is no way the Binder driver can find out them out. offsets_size
is an array of such offsets. Project Treble introduced a new form of Binder data serialization mode called scatter-gather. With scatter-gather the payloads may not be copied and packed into the buffer before the Binder transaction. Instead, the payloads remain in their original location and the binder_buffer
object contains some pointers to those locations. This mechanism avoids two rounds of data copying in user space and thus can improve transaction speed for large transactions. extra_buffers_size
is the total size of those scatter-gather payload that are pointed to by pointers in binder_buffer
. Scatter-gather is not currently used by Android framework so we will always assume there is no scatter-gather buffer.
Now let’s go back to binder_alloc_mmap_handler
:
binder_alloc_mmap_handler
sets up a kernel data buffer for the calling process. This function now should be easy to understand with previous introductions about binder_alloc
and binder_buffer
. As we can see the driver can allocate at most 4 MB buffer for a process. Android framework in reality requests about 1 MB for a process. The actual size will be the smaller of 4 MB and the size user space requested in mmap
system call. Line 16 to 21 allocates a single binder_buffer
structure representing the whole space allocated and inserts it into the binder_alloc
structure. Line 22 limits the total space that can be used by asynchronous transactions to half total allocated buffer size.
This function creates a list of binder_lru_page
s to manage physical page frames but it actually doesn't request any physical pages from the kernel. This is Binder driver's approach to demand paging. Later when a Binder transaction really needs physical memory the driver will request physical page frames and fill in page table entries to create address mapping. But during mmap
system call no physical pages will be allocated.
binder_ioctl walk through
The parameters of ioctl
system call include a command type and an associated argument. Let's look at several important ioctl
commands. We will not look at BINDER_WRITE_READ
command in this article since it is too complicated to analyze. The details of BINDER_WRITE_READ
will be unveiled in later articles. BINDER_SET_MAX_THREADS
sets the maximum number of Binder threads that Binder driver can request user space to start for current process. Current Android version sets it to 15 when calling ioctl
. BINDER_VERSION
returns the current version of Binder driver to use space so that user space program can do compatibility checking. BINDER_SET_CONTEXT_MGR_EXT
or BINDER_SET_CONTEXT_MGR
is used to register current process as the context manager of current Binder device instance. The topic of context manager deserves a dedicated article so we will not talk about it in this article.
Binder native and proxy objects
I briefly touched on this topic earlier in this article. However we need to look into the internals of these objects to get a better understanding of them. We will have a deeper look into the object oriented design of Binder objects in user space, particularly in Android framework. For each kind of object, We will analyze both C++ implementation and Java implementation of it. Be aware, the relationship between these classes are convoluted so be patient.
Android framework provides utility classes to create Binder objects both in native and Java code. In native user space the IBinder
is the base class of all kinds of Binder objects, both proxy objects and native objects. All native Binder objects inherit from BBinder
and all proxy Binder objects inherit from BpBinder
. The IBinder
interface inherits from RefBase
in smart pointer framework which enables object reference counting. It's not related to Binder
so we will disregard it in this article. The article "Smart pointers" has a detailed explanation of this RefBase
class.
A class who inherits from IBinder
gains the capability of crossing process boundaries, a.k.a. remotability. The most important interface method is transact
which triggers a cross process transaction. There are four parameters to this method:
The code
argument is the transaction code of this transaction. Take the RemoteService
in the HamKing app for example, the requestOrder
, cancelOrder
and pickupOrder
each has a different transaction code. The aidl
tool will automatically generate these codes. The data
argument is the input data for this transaction and reply
is the output data. The most important bit mask for flags
argument is FLAG_ONEWAY
by setting which you make this transaction asynchronous. If it is set then reply
will be null and the caller process won't be waiting for the target process to send a reply.
The isBinderAlive
and pingBinder
methods check whether the target native Binder object is still alive. Use linkToDeath
to register a death listener of the target native object and use unlinkToDeath
to remove it. Note that when you use the four methods above to work with an object's lifetime, the subject is always the underlying native Binder object.
Use localBinder
and remoteBinder
to "cast" this object to a native object and proxy object respectively. For BpBinder
, localBinder
returns null and remoteBinder
returns itself; for BBinder
, localBinder
returns itself and remoteBinder
returns null.
BBinder
is the base class of all native Binder objects. It is the class that implements the actual transaction business logic. BpBinder
is the base class of all proxy Binder objects, basically it is a reference to a native Binder objects running in another process. Proxy objects don't implement the actual transaction business logic, they just delegate the call to the underlying BBinder
in a remote process with the help of Binder driver.
The BBinder
adds a new method onTransact
to the IBinder
interface. Subclasses of BBinder
should override onTransact
to implement the actual transaction logic. Again take our RemoteService
as an example, the requestOrder
, cancelOrder
and pickupOrder
are actually dispatched from onTransact
. Of course BpBinder
doesn't have the onTransact
method since it doesn't implement the transaction business logic.
The BpBinder
adds a bunch of death notification related classes and methods. BpBinder
needs to have the capability of listening to the death of a remote BBinder
. The term "death" in reality means the remote process exits unexpectedly or proactively. When a process dies, all BBinder
instances in it are gone, of course. The Obituary
class just wraps around DeathRecipient
and it represents a listener for remote BBinder
death and mObituaries
stores a list of such listeners. mAlive
and mObitsSent
are used to indicate whether the remote BBinder
has died. The reportOneDeath
basically just invokes the death callbacks on a DeathRecipient
.
A new field mHandle
is added in BpBinder
and it is critical to understand what it is. To understand it we need to first understand how Binder driver manages user space native and proxy objects. Binder driver uses binder_node
structure to describe a BBinder
in user space and a BpBinder
in user space corresponds to a binder_ref
in Binder driver. (In one process, multiple BpBinder
s referencing the same remote BBinder
will only have one binder_ref
in Binder driver.) Let's take a look inside binder_node
structure:
The cookie
field in binder_node
is the user space address of the BBinder
object and ptr
is the address of a weak pointer object inside BBinder
. Weak pointers will be discussed in the article "Smart pointers", but what's important is that Binder driver manages BBinder
objects by storing their user space memory addresses. binder_proc
stores all binder_node
instances in a red black tree and the sorting key is the ptr
value. This works because in a give process, any two BBinder
objects will have different virtual addresses. The refs
field is a hash table of all binder_ref
instances that are referencing the binder_node
. The node
field in binder_ref
points to the binder_node
that it is referencing and the desc
field in binder_ref_data
is the user space handle that acts as an identifier to the binder_node
it points to. This desc
is basically the mHandle
in BpBinder
. So basically, the desc
value uniquely identifies the target binder_node
it points to.
But how is this handle value generated though? The driver uses the memory address of BBinder
as the key for binder_node
but it cannot use that address as the handle in binder_ref
. That's because it is a virtual address in another process so it has no meaning in current process. How the driver generates this handle is simple: it is just an incremental integer starting from 1. When the driver allocates the handle for a binder_ref
it just chooses the smallest integer that is unused for the current process. The handle has a per-process scope: If a process "A" creates a BBinder
and two other processes "B" and "C" both reference this BBinder
then the desc
value of binder_ref
in "B" and "C" can be the same. binder_proc
has a field refs_by_desc
that sorts all binder_ref
instances by the handle value. So given a handle value the driver can quickly find the corresponding binder_node
it is pointing to.
The binder_init_node_ilocked
is where driver initializes a binder_node
and stores it sorted by user space address:
flat_binder_object
is a serialized BBinder
from user space and line 28 to 29 stores its user space addresses in binder_node
. Line 14 to 27 finds the right location for the new binder_node
in the tree.
The binder_get_ref_for_node_olocked
is where driver initializes a binder_ref
and allocate a handle value for it:
Line 16 to 21 traverses the tree to find the smallest unused integer as the handle value for the new binder_ref
and line 23 to 35 adds the new binder_ref
to the tree of binder_ref
instances of the corresponding process.
We have seen how kernel uses the binder_ref
and binder_node
structures to manage user space Binder objects, now let's get back to Android framework to see how BBinder
implements it's interface methods:
The transact
method handles certain types of transaction codes that are common to all BBinder
instances. Otherwise onTransact
is called to handle service specific transaction codes that subclass of BBinder
should implement. localBinder
just returns itself, of course, since BBinder
is a local Binder. However, the implementation of other methods are dummy. The semantic of isBinderAlive
and pingBinder
are to check the liveness of the target BBinder
. If this two methods, or any methods in a BBinder
, can run then this BBinder
is alive. The linkToDeath
and unlinkToDeath
just returns INVALID_OPERATION
. The semantic of death notification is to get a callback when the underlying BBinder
has died. If a process is dead, how can you invoke a callback in this process to tell it the fact that this process is dead?
These methods obviously don’t make sense in a BBinder
so why are they in the base class IBinder
not the BpBinder
class? The truth is that in Android system a program should seldom make assumptions about whether a Binder object they gets a hold of is remote or local. In another word the polymorphic type a program uses to reference a Binder should always be IBinder
. Also the Android framework always uses IBinder
type in the programming interfaces it exposes. Quoting from a post by Dianne Hackborn, a lead engineer in Android and formerly OpenBinder [2]:
“Separate components, like the window manager or surface flinger, may be switched between running in the same process or different processes with no change to their code. For example, in the current android platform these two components run in the same process, but we also have had run them in other processes and would like to do so on higher-end systems where there is more memory. This is not strictly a feature of the kernel part of the binder, but the IPC semantics it provides greatly ease its implementation: dispatching transactions to thread pools, synchronous calls with recursion across processes, etc.”
As we can see Binder framework is designed to have the capability of switching Binders between processes. In previous discussions I assume a native Binder and it’s client run in different processes. But in reality they can be in the same process and it is common that they run in the same process. For example, most of the services that Service Manager manages run in a system process called system_server
and they use each other's interfaces all the time. But it's possible that one day a system service will be moved to a separate process. For example, in Android 2.x the SurfaceFlinger
service runs inside system_server
but later in Android 4.x it is moved to a standalone process. Since all code in system_server
only uses IBinder
to reference SurfaceFlinger
, no code change needs to be made even though the concrete type of all IBinder
s referencing SurfaceFlinger
are changing from BBinder
to BpBinder
.
That’s why IBinder
includes those interfaces that don't make sense for BBinder
. Let's take a look at how BpBinder
implements the interfaces:
The transact
method in BpBinder
checks whether the corresponding BBinder
it references is still alive. If mAlive
is unset before it will just return DEAD_OBJECT
because if a process dies it dies forever. Even if the same program is restarted and the same BBinder
instances are recreated in the new process, they are completely new for Binder driver. BpBinder
invokes a transact
method on IPCThreadState
to initiate a Binder transaction with Binder driver. The driver will eventually causes the transact
method on the target BBinder
to be called and gets the result back in reply
parameter. The linkToDeath
also uses IPCThreadState
to register a listener for the remote BBinder
object. sendObituary
will be called by IPCThreadState
when a death notification is received from Binder driver. BpBinder
will callback into every DeathRecipient
registered.
A BBinder
is a dummy native Binder object that can be discovered by other processes. A BpBinder
is a dummy proxy Binder object that is capable of referencing a BBinder
in a remote process. They don't implement any custom service interfaces. In order to implement a service that's discoverable by another process, we need to use BnInterface
class. In order to implement a service proxy that's capable of referencing a remote BnInterface
and delegating service calls to the remote BnInterface
we need to use BpInterface
:
IInterface
is the base class any custom service provider should extend from. For an example, if we use aidl-cpp
tool to generate a C++ version of the ICreditCard
interface, then it will look like this:
The INTERFACE
generic type must be a subclass of IInterface
like the ICreditCard
above. The BnInterface
class inherits from BBinder
so it gains the capability of being referenced remotely. It inherits from INTERFACE
so it inherits the service methods listed in INTERFACE
. If you are writing a service, Binder framework cannot help you any further because Binder won't know how you want to implement your services. The major work to write a Binder service is to override the onTransact
method to implement the business logic of the services. You don't need to hand write every line of code though, the aidl-cpp
tool can auto-generate a lot of boilerplate code for you.
The BpInterface
inherits from a new class BpRefBase
. The mRemote
field in BpRefBase
is a BpBinder
that points to the corresponding remote BBinder
. BpInterface
also inherits from INTERFACE
so it also needs to implement all service methods in it. But BpInterface
will just delegate all calls to mRemote
who will then use Binder driver to pass the calls to the remote BnInterface
. But why can't BpInterface
inherits from BpBinder
instead of wrapping around it? We should understand that the remotability of Binder objects that are materialized by BBinder
and BpBinder
are facilitated by Binder driver. The connections between them are managed by Binder driver. However, the Binder services represented by a subclass of IInterface
is totally a user space concept and Binder driver knows nothing about it. After a BBinder
passes through process boundary, all that a remote process receives is the integer handle value. All information this handle value carries is that it references a remote BBinder
. No Binder service information is in it so Binder framework cannot initialize a BpInterface
from the handle.
So BpInterface
cannot inherit from BpBinder
to represent the "is a" relationship. It can only use a delegation pattern to represent the "has a" relationship. But how can Binder framework ensure that the BpBinder
that BpInterface
contains points the right BnInterface
? No, Binder cannot ensure that. For example, in the HamKing project server app module, the onBind
callback returns a remote or local service based incoming Intent
action:
What if the client app code passes the wrong action name in the Intent
or the server code has a bug so that the mLocalService
is passed to the client? Binder driver won't know it at all and the client won't know it before it finds out the remote service is not working as expected. However, even though Binder framework cannot prevent this kind of error but there are utility methods for the server to validate incoming transactions.
We just concluded the key C++ utility classes for Binder objects, now let’s step into the Java world. But no worry, the concepts are almost identical and each Java class has a peer native version we already introduced. This is why I can use the HamKing sample project, which is written in Java, to explain concepts in C++ framework all the time.
This is Java version of IBinder
:
As you can see the interface is almost identical so no further explanation is needed. The Binder
java class peers C++ BBinder
:
The mObject
is the memory address of its native peer. The native peer is created via a native function getNativeBBinderHolder
. When a transaction is initiated by the local process the transact
is invoke to handle it. When a transaction is called by a remote process execTransact
will be called from native code to handle it. But they are basically doing the same thing: call the onTransact
method that a base class should implement. Incoming transaction from another process will always invoke execTransact
since the call flow needs to go from Binder driver to native user space, then to ART virtual machine. Let's take a look at how getNativeBBinderHolder
is implemented:
As we can see the the mObject
field in Java Binder
class points to a JavaBBinderHolder
object which lazily initializes a JavaBBinder
. The JavaBBinder
just overrides the onTransact
method of BBinder
so that execTransact
in Java is invoke on incoming transactions.
The Java peer of BpBinder
is Java BinderProxy
class:
The mNativeData
points to it's native peer and BinderProxy
just invokes the native methods in the corresponding BpBinder
. Whenever a Java IBinder
is returned from JNI, javaObjectForIBinder
will be invoked:
The val
parameter will be a BpBinder
. As we can this JNI method calls the getInstance
method in BinderProxy
and the mNativeData
in BinderProxy
will be set to the newly created BinderProxyNativeData
. BinderProxyNativeData
has a field mObject
that points to the BpBinder
. After this a BinderProxy
and a BpBinder
are linked together.
JavaBBinderHolder
is never created in native code and it's always created when a Java Binder
is created. BinderProxy
is never created in Java code and it's always created by native code. This is easy to understand if you think about the transaction data flow: The integer handle value of a proxy needs to be fetched from Binder driver before creating a BpBinder
. Only after that a BinderProxy
in Java can be created.
The Java class IInterface
peers C++ IInterface
:
The BnInterface
and BpInterface
don't have matching Java classes in Android framework. Their corresponding classes are generated by aidl
tool instead. For example, aidl
tool will generate such a Java file for IOrderSession.aidl
in HamKing:
aidl
generates a IOrderSession
interface which extends from IInterface
and adds the registerListener
method to it. It also generates a Stub
class which corresponds to the BnInterface
in C++, and a Proxy
class which corresponds to the BpInterface
in C++. The Proxy
class contains a delegate mRemote
which is always a BinderProxy
. Developer should provide a concrete class that extends from the Stub
class to implement the business logic of the service methods. In this case it is the registerListener
.
The user space utility classes that facilitate Binder objects is complicated but they are critical for understanding Binder. I hope you have a good understanding of them by now. Good luck!