Binder Architecture and Core Components

Baiqin Wang
The Startup
Published in
22 min readFeb 3, 2021

--

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,vndbinderwhich 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_works. 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_buffers. 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_pages 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 BpBinders 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 IBinders 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!

External links

[1] https://github.com/androidonekb/HamKing

[2] https://lkml.org/lkml/2009/6/25/3

--

--