Binder is a very important part of Android. Many of the features in Android are built on Binder mechanisms. In this article, Binder in Android will be analyzed for its role in system architecture. Then, we’ll briefly explain why Android developed a separate cross-process communication mechanism from an underlying implementation perspective. Finally, an AIDL example is given to illustrate how Binder can be used to communicate.

What are Binders? Why is it important for Android?

“What is a Binder? Why is it so important for Android?” Before we answer that question, let’s talk about something else.

Have you ever wondered why we call the startActivity() method when launching a page in Android and then pass in an Intent? What if instead of passing values around like this, we just write them as static variables? This is a question I’ve been asked before.

Let’s answer the second of these two questions first. Using static variables to pass values is fine in most cases, but it is important to release resources immediately after using values, otherwise it will take up too much memory, or even OOM. However, it is not applicable in a special case, that is, across processes. This is because the scope of a static variable is only the process in which it resides, and it is cross-process access when accessed by other processes. For the first question, the startup process of an Activity in Android is much more complicated than we might think, involving cross-process communication. When we call the startActivity() method, all of our “intents” are filtered through layers until they are processed in a place called AMS. Once that’s done, you can call the same process you started the page with across processes for subsequent processing, calling back lifecycle methods such as onCreate().

Launching an Activity involves two important Android communication mechanisms, Binder and Handler, which we’ll examine in a future article.

Here is a simple diagram to illustrate the process of starting an Activity:

When we call the startActivity() method, we first get ActivityManagerService (AMS) from the ServiceManager and pass ApplicationThread to AMS as an argument. Then execute AMS’s method to start the Activity. (Execute another process’s method in our application process.)

AMS is global and is started at system startup time. Get the global variable from the ServiceManager when we use it. When we call its methods, the method specific execution logic is executed in the system process. The ApplicationThread we pass in acts like a messenger. When AMS finishes processing and decides to call back the Activity’s lifecycle method, it calls the ApplicationThread method directly (this calls our application process in another process). This implements our Activity’s lifecycle callback.

After watching the above process, maybe some students will feel that. Binder is essential to Android, but we don’t use Binder. In fact, we just didn’t use Binder directly. The following picture shows how we actually used Binder in our development process.

In most cases, we are interacting with managers that actually use binders internally to communicate across processes. As shown above, when we call Manager, the Manager gets another process’s Stub object from the Binder driver through the proxy class. We then use this Stub object to remotely call another process’s methods. The process is encapsulated without our awareness, and the mechanism for cross-process communication (IPC) is the Binder mechanism.

What is a Stub? Stubs are part of the AIDL specification. AIDL provides a template for using Binder. This definition is used extensively in the Android system to accomplish cross-process communication. You’ll see how AIDL works when we introduce it later.

2. Why Binder and not other communication mechanisms?

Android is based on Linux, which already has a number of IPC mechanisms, such as: Pipes, Signals and traces, sockets, Message queues, shared Memory, and Semaphore. So why did Android go out of its way to create an IPC mechanism? There’s a reason for this, of course:

  1. Efficiency: As a universal interface, Socket has low transmission efficiency and high cost. It is mainly used for inter-process communication across the network and low-speed communication between processes on the local machine. Message queues and pipes adopt store-and-forward mode, that is, data is copied from the sender cache to the cache created by the kernel, and then copied from the kernel cache to the receiver cache at least twice. Although shared memory does not require copying, it is difficult to control and use. Binders require only one copy of data and are second only to shared memory in performance.

  2. Stability: Binder based on C | S architecture, the Client (Client) what are the requirements to the Server (Server) to complete, clear architecture, strong responsibilities and are independent of each other, natural stability is better. Shared memory does not require copying, but is controlled and difficult to use. Binder mechanisms are superior to memory sharing from a stability perspective.

  3. Security: Binder by adding the status symbol for the client within the kernel layer UID | PID, as a symbol of identity, to safeguard the security of communication. Traditional IPC access points are open and private channels cannot be established. For example, the name of the named pipe, the SystemV key, the Socket IP address, or the file name are all open. As long as a program knows these access points, it can establish a connection with the peer end.

In addition to the above reasons, Binder has many other features, such as: 1). With reference counting, Binder owners are notified that they can release a Binder when it is no longer referenced by any client. This is suitable for Android applications where resources are often recycled due to insufficient resources. 2). It maintains a thread pool internally; 3). Remote methods can be triggered as local methods. 4). Support synchronous and asynchronous (ONEway) trigger model; 5). It can be described and developed using AIDL templates.

3. Binder model, 4 main roles in Binder

There are four main roles in the Binder model: Client, Server, drive and ServiceManager Binder. The overall structure of the Binder is based on the structure of the C | S, we begin the process of the Activity, for example, each application will interact with the AMS, When they have access to AMS’s Binder, it’s like they have access to the network interface. A Binder is a Server, a Client is a Client terminal, a ServiceManager is a DOMAIN name Server (DNS), and a driver is a router. The Server, Client, and ServiceManager run in user space, and the driver runs in kernel space.

When our system starts up, it will start each service when the SystemServer process is started, including AMS. They’re put into a hash table, and the key of the hash table is a string. This allows us to find the corresponding service by its string name. These services are individual Binder entities, which in the case of AMS is the iActivityManager.stub instance. Once started, these services act like servers in the network waiting for users to access them.

For the ServiceManager here, it is also a service, but it is special in that it is registered before all other services and only once. It is used to look up services from the hash table by the name of the string and to register services with the hash table at system startup time.

So, we can use the diagram above to describe the Binder model as a whole: Binder drivers register all required services with the Binder driver (ServiceManager is registered first, followed by other services), and when a client needs to use a service, You also need to interact with Binder drivers, which look up the specified service in the ServiceManager by name and return it to the client program for use.

4. Principle of Binder

Above we reviewed the Binder model and why the system is designed with a communication mechanism. Have you ever wondered how Binder’s magic works? Here we review the internal implementation of Binder.

First of all, Binder is a very complex implementation process, with 200 pages devoted to it in the book Android Source Code Scenario. We are not going to go into the details of how it is implemented, we are just going to do a simple analysis of some of the content, and we do not want to involve a lot of code.

4.1 Structure of inDER related system source code

Then, we need to look at the position of Binder related core classes in the source code,

-framework
    |--base
        |--core
            |--java--android--os  
                              |--IInterface.java
                              |--IBinder.java
                              |--Parcel.java
                              |-- IServiceManager.java
                              |--ServiceManager.java
                              |--ServiceManagerNative.java
                              |--Binder.java  
            |--jni
                |--android_os_Parcel.cpp
                |--AndroidRuntime.cpp
                |--android_util_Binder.cpp
    |--native
        |--libs--binder         
                  |--IServiceManager.cpp
                  |--BpBinder.cpp
                  |--Binder.cpp             // Concrete implementation of Binder
                  |--IPCThreadState.cpp
                  |--ProcessState.cpp
        |--include--binder                  // Mainly header files
                      |--IServiceManager.h
                      |--IInterface.h
        |--cmds--servicemanager
                    |--service_manager.c    // The ServiceManager used to register the service
                    |--binder.c
-kernel-drivers-staging-android
                         |--binder.c        
                         |--uapi-binder.h
Copy the code

4.2 Several functions that are critical to Binder implementation

When we look at the binder.c source code, or at binder related operations, we often see several operations: IOCtl, mmap, and open. So what do these operators mean?

H >, #include

, #include

, and #include

.


int open(const char * pathname, int flags);
int open(const char * pathname, int flags, mode_t mode);
Copy the code

Here the pathname represents the file path; Flag indicates the opening mode. Mode Indicates the enabled mode and permission. If all permissions to be checked pass the check, return 0, indicating success. If only one permission is denied, return -1.

Then there is the ioctl instruction, which needs to include the #include

header. Ioctl is a function in the device driver that manages the I/O channel of the device and sends control and configuration commands to the device. Its function is defined as follows:

int ioctl(intFd, ind CMD,...).;Copy the code

Fd is the file identifier returned by the user program using the open function when opening the device, CMD is the user program’s command to control the device, and the ellipsis behind it is some supplementary parameters, usually one at most. Whether this parameter is relevant to the meaning of CMD.

Finally, there is the Mmap function, which implements memory mapping. #include

. The corresponding munmap function. Their functions are defined as follows,

void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
int munmap(void* start,size_t length);
Copy the code

The meaning of the parameters here is:

  1. Start: indicates the start address of the mapping area. If this parameter is set to 0, the system determines the start address of the mapping area.
  2. Length: indicates the length of the mapping area. The length unit is byte. If the length is less than one memory page, it is processed by one memory page.
  3. Prot: Expected memory protection flag that cannot conflict with the open mode of the file. Is one of the following values that can be reasonably combined by an O r operation;
  4. Flags: Specifies the type of mapping object, mapping options, and whether the mapping page can be shared. Its value can be a combination of one or more of the following bits;
  5. Fd: indicates a valid file description. Generally is by theopen()The flags function returns the MAP_ANON value, which can also be set to -1, indicating that anonymous mapping is performed.
  6. Off_toffset: the starting point of the content of the mapped object.

On success, mmap() returns a pointer to the mapped region and munmap() returns 0. On failure, mmap() returns MAP_FAILED[with (void *)-1] and munmap() returns -1.

4.3 ServiceManger start

ServiceManager in Binder is not Java layer ServiceManager, but Native layer. Starting the ServiceManager is created by the init process by parsing the init.rc file. The service_manager.c file in the source directory above is found on startup and its main() method is called.

// platform/framework/native/cmds/servicemanager.c
int main(int argc, char** argv)
{
    struct binder_state *bs;
    char *driver;

    if (argc > 1) {
        driver = argv[1];
    } else {
        driver = "/dev/binder";
    }
    // 1. Enable the binder driver
    bs = binder_open(driver, 128*1024);
    // ...
    // 2. Set the current ServiceManger to the context
    if (binder_become_context_manager(bs)) {
        return - 1;
    }
    // ...
    // 3. Start the binder loop to enter the continuous listening state
    binder_loop(bs, svcmgr_handler);
    return 0;
}
Copy the code

ServcieManager startup process is the above three steps, without too much description. Below we give the concrete implementation of these three methods. In the following code you will see the three functions we introduced earlier in action. It’s not a problem for you to understand after you have the previous setup 🙂

// platform/framework/native/cmds/servicemanager.c
struct binder_state *binder_open(const char* driver, size_t mapsize)
{
    struct binder_state *bs;
    struct binder_version vers;
    bs = malloc(sizeof(*bs));
    if(! bs) { errno = ENOMEM;return NULL;
    }
    // Open the device driver
    bs->fd = open(driver, O_RDWR | O_CLOEXEC);
    if (bs->fd < 0) {
        goto fail_open;
    }
    // Send a command to the driver to obtain binder version information
    if ((ioctl(bs->fd, BINDER_VERSION, &vers) == - 1) || (vers.protocol_version ! = BINDER_CURRENT_PROTOCOL_VERSION)) {goto fail_open;
    }
    bs->mapsize = mapsize;
    // Through the system call, mmap memory mapping, mmap must be an integer multiple of page
    bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
    if (bs->mapped == MAP_FAILED) {
        goto fail_map;
    }
    return bs;
fail_map:
    close(bs->fd);
fail_open:
    free(bs);
    return NULL;
}
Copy the code

In the above code, you use the open() function to open the device driver (which is an operation to open a file), and then use the ioctl() function to send instructions to the device driver to get device information. Finally, memory mapping is implemented through the mmap() function, passing in the file descriptor described above. Here binder_state is a structure, defined as follows. It’s a state of binder. We can also see the assignment of its three variables above.

// platform/framework/native/cmds/servicemanager.c
struct binder_state
{
    int fd;
    void *mapped;
    size_t mapsize;
};
Copy the code

Of course, in the code above, we see the long-lost goto instruction again. They are mainly used to handle situations where exceptions occur.

Once the driver is turned on, registering as a context is easier,

// platform/framework/native/cmds/servicemanager.c
int binder_become_context_manager(struct binder_state *bs)
{
    return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}
Copy the code

Is an IOCtl function that registers the current ServiceManager as a context using the BINDER_SET_CONTEXT_MGR directive.

The final step is to start the Binder cycle. The logic is not as complicated as you might think, which is to start the for loop,

// platform/framework/native/cmds/servicemanager.c
void binder_loop(struct binder_state *bs, binder_handler func)
{
    int res;
    struct binder_write_read bwr;
    uint32_t readbuf[32];

    bwr.write_size = 0;
    bwr.write_consumed = 0;
    bwr.write_buffer = 0;

    readbuf[0] = BC_ENTER_LOOPER;
    // Send BC_ENTER_LOOPER to binder drivers to internally call ioctl functions
    binder_write(bs, readbuf, sizeof(uint32_t));

    for (;;) {
        bwr.read_size = sizeof(readbuf);
        bwr.read_consumed = 0;
        bwr.read_buffer = (uintptr_t) readbuf;
        // use iotCL to read
        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
        if (res < 0) {
            break;
        }
        / / parsing
        res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
        if (res == 0) {
            break;
        }
        if (res < 0) {
            break; }}}Copy the code

As you saw above, the function will send commands to the Binder driver in binder_write() to start the loop. In fact, the internal call ioctl function is implemented. The program then starts a loop that reads and parses. This is a typical server operation.

ServiceManager writes commands to Binder. Binder writes commands to Binder. ServiceManager writes commands to Binder. Of course, it is implemented in the driver, the detailed process can be viewed in the Binder driver part of the source code.

4.4 Cross-process communication with Binder

The following uses AMS as an example to illustrate the implementation of cross-process communication with Binder. First, when we call the startActivity() method, we end up going into ActivityManager to get AMS,

    // platform/framework/base/core/java/android/app/ActivityManager.java
    final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
    final IActivityManager am = IActivityManager.Stub.asInterface(b);
    return am;
Copy the code

ServiceManger is used to look up AMS by name, find Binder objects, and convert them to AMS for use. Earlier, we also said that the SeerviceManager used to find AMS is itself a service. So, its methods here are also implemented with Binder. So, let’s start with the getService() method here.

    // platform/framework/base/core/java/android/os/ServiceManager.java
    public static IBinder getService(String name) {
        try {
            IBinder service = sCache.get(name);
            if(service ! =null) {
                return service;
            } else {
                returnBinder.allowBlocking(rawGetService(name)); }}catch (RemoteException e) { / *... * / }
        return null;
    }
Copy the code

Binder is first tried to fetch from the cache, if not, it is fetched remotely. The rawGetService() method is used to fetch Binder remotely.

    // platform/framework/base/core/java/android/os/ServiceManager.java
    private static IBinder rawGetService(String name) throws RemoteException {
        final IBinder binder = getIServiceManager().getService(name);
        // ...		
        return binder;
    }

    // platform/framework/base/core/java/android/os/ServiceManager.java
    private static IServiceManager getIServiceManager(a) {
        if(sServiceManager ! =null) {
            return sServiceManager;
        }
        sServiceManager = ServiceManagerNative
                .asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
        return sServiceManager;
    }
Copy the code

The rawGetService() method uses ServiceManagerNative’s getService() method to fetch binders remotely. ServiceManagerNative here is essentially a proxy class, its actual logic is by BinderInternal getContextObject () returns the Binder.

In case you’re confused, Binder… Let me make this clear. Finding AMS is actually a cross-process call, meaning that the actual lookup logic is implemented in another process and therefore requires a Binder to communicate. And the remote object that looks up AMS is actually what we called a ServiceManager (Native layer rather than Java layer, Java layer ServiceManager is a proxy class that is used to fetch services remotely).

. Therefore, according to the above description, BinderInternal getContextObject () returns the Binder objects should be remote. So the method goes into the Native layer,

// platform/framework/base/core/jni/android_util_Binder.cpp
static jobject android_os_BinderInternal_getContextObject(JNIEnv* env, jobject clazz)
{
    sp<IBinder> b = ProcessState::self() - >getContextObject(NULL);
    return javaObjectForIBinder(env, b);
}
Copy the code

Is ProcessState::self() familiar? If you remember, we introduced Android in our last article when we introduced the startup process. We used it to start thread pools with Binder. The self() method here is actually used to get a singleton. We can go directly to getStrongProxyForHandle() from getContextObject(). As we can see from the following method, BpBinder’s Create () method is called to create and return an instance of BpBinder, our ServiceManager.

// plaftorm/framework/native/libs/binder/ProcessState.cpp
sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
{
    sp<IBinder> result;
    AutoMutex _l(mLock);
    handle_entry* e = lookupHandleLocked(handle);
    if(e ! =nullptr) {
        IBinder* b = e->binder;
        if (b == nullptr| |! e->refs->attemptIncWeak(this)) {
            // ...
			/ / call BpBinder
            b = BpBinder::create(handle);
            e->binder = b;
            if (b) e->refs = b->getWeakRefs(a); result = b; }else {
            result.force_set(b);
            e->refs->decWeak(this); }}Copy the code

When we have ServiceManager’s Binder, we can call its getService() method to get the service,

    // platform/framework/base/core/java/android/os/ServiceManagerNative.java
    public IBinder getService(String name) throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(IServiceManager.descriptor);
        data.writeString(name);
        mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);
        IBinder binder = reply.readStrongBinder();
        reply.recycle();
        data.recycle();
        return binder;
    }
Copy the code

The mRemote here is the BpBinder returned earlier, and its transact() method is called with a method flag GET_SERVICE_TRANSACTION passed in.

// platform/framework/native/libs/binder/BpBinder.cpp
status_t BpBinder::transact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    if (mAlive) {
        status_t status = IPCThreadState::self() - >transact(mHandle, code, data, reply, flags);
        if (status == DEAD_OBJECT) mAlive = 0;
        return status;
    }
    return DEAD_OBJECT;
}
Copy the code

Obviously, the self() method of IPCThreadState is called to get a singleton object and then its transact() method is called to continue the method execution.

// platform/framework/native/libs/binder/IPCThreadState.cpp
status_t IPCThreadState::transact(int32_t handle, uint32_t code, 
    const Parcel& data, Parcel* reply, uint32_t flags)
{
    status_t err;
    // ...
    err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, nullptr);
    // ...
    if ((flags & TF_ONE_WAY) == 0) { // Call of type OneWay, synchronous
        // ...
		if (reply) {
            // Wait for the response
            err = waitForResponse(reply);
        } else {
            Parcel fakeReply;
            err = waitForResponse(&fakeReply);
        }
        IF_LOG_TRANSACTIONS() {
            TextOutput::Bundle _b(alog);
            if (reply) alog << indent << *reply << dedent << endl;
            else alog << "(none requested)"<< endl; }}else { / / asynchronous
        err = waitForResponse(nullptr.nullptr);
    }
    return err;
}
Copy the code

The writeTransactionData() method is called to write data to the Parcel. The waitForResponse() method is then entered to process the results of the interaction with the ServiceManager. Where the real interaction happens is in the talkWithDriver() method,

// platform/framework/native/libs/binder/IPCThreadState.cpp
status_t IPCThreadState::talkWithDriver(bool doReceive)
{
    if (mProcess->mDriverFD <= 0) {
        return -EBADF;
    }

    binder_write_read bwr;
    const bool needRead = mIn.dataPosition() >= mIn.dataSize(a);const size_toutAvail = (! doReceive || needRead) ? mOut.dataSize() : 0;

    bwr.write_size = outAvail;
    bwr.write_buffer = (uintptr_t)mOut.data(a);if (doReceive && needRead) {
        bwr.read_size = mIn.dataCapacity(a); bwr.read_buffer = (uintptr_t)mIn.data(a); }else {
        bwr.read_size = 0;
        bwr.read_buffer = 0;
    }

    if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;

    bwr.write_consumed = 0;
    bwr.read_consumed = 0;
    status_t err;
    do {
        // Interact with Binder drivers through ioctl reads and writes
        if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
            err = NO_ERROR;
        else
            err = -errno;
        if (mProcess->mDriverFD <= 0) { err = -EBADF; }}while (err == -EINTR);
    // ...
    return err;
}
Copy the code

The binder_write_read structure is used to exchange data with the Binder devices. The binder_write_read structure communicates with mDriverFD through IOCTL, which is the actual process of reading and writing data with the Binder drivers. First send a request to the Service Manager process to query the service (BR_TRANSACTION). The Service Manager then listens in on the previously opened loop and processes it using the svcmgr_handler() method.

// platform/framework/native/cmds/servicemanager.c
int svcmgr_handler(struct binder_state *bs, struct binder_transaction_data *txn, struct binder_io *msg, struct binder_io *reply)
{
    // ...
    switch(txn->code) {
        case SVC_MGR_GET_SERVICE:
        case SVC_MGR_CHECK_SERVICE:
            s = bio_get_string16(msg, &len);
            if (s == NULL) {
                return - 1;
            }
            handle = do_find_service(s, len, txn->sender_euid, txn->sender_pid);
            if(! handle)break;
            bio_put_ref(reply, handle);
            return 0;
        case SVC_MGR_ADD_SERVICE: // ...
        case SVC_MGR_LIST_SERVICES: // ...
    }
    return 0;
}
Copy the code

Obviously, the code is retrieved from binder_transaction_data, SVC_MGR_GET_SERVICE, and then the do_find_service() method is used to find the service. Then binder_send_reply() returns the result to the originator.

4.5 Reasons for efficient communication with binders

The communication process of Binder has been reviewed above, and there seems to be no evidence to prove the effectiveness of Binder. So what does Binder do to make it so effective?

In fact, Binder is more efficient than the code above shows. Because our code above does not cover the Binder driver part. As we described earlier, ServiceManager, clients, and servers actually use binders to drive this intermediary to interact with each other. Where Binder is effective is in the Binder drive part.

Binder Principles for Android Application Engineers

As illustrated, Binder drivers map the kernel cache to the data receive cache and the user space address of the receiving process when two processes need to communicate with each other. In this way, when data is copied from one user space to the kernel buffer, it is copied to another user space. In this way, only one copy is required, eliminating the step of temporary storage in the kernel and doubling the performance. Memory mapping is achieved by the above mmap() function.

4. Use of Binder

4.1 Proxy Mode

A Binder is essentially an underlying means of communication, independent of specific services. To provide concrete services, a Server must provide a set of interface functions that allow clients to use various services through remote access. In this case, the proxy design pattern is usually adopted: the interface functions are defined in an abstract class, and the Server and Client implement all interface functions based on this abstract class. The difference is that the Server side is the real function implementation, while the Client side is the wrapper around the request for remote calls to these functions. To simplify this design pattern, Android provides AIDL for us to use. In the following sections, we will cover AIDL and some basic ways to use it.

4.2 AIDL

AIDL (Android Interface Definition Language) is a file format designed to simplify the use of Binders. With Binder, you just create a file with the suffix.aidl and define methods like interfaces. Once defined, the various files required by Binder are generated using the tool aidl. Exe. Of course, our AS already integrates aidl. Exe for us, so all you need to do is define the AIDL file and compile it to produce the files you need to use Binder. Of course, it is possible to write Java files for Binder without using AIDL.

AIDL is an interface definition language that differs from the way interfaces are defined in Java. Here’s an example of how AIDL can be used.

Here we simulate a note-management class that implements the IPC effect by interacting with a remote Service in the Activity. Here, we first define the data entity Note, which contains only two fields and implements Parcelable. Note here is in the directory is me. Shouheng. The aidl, then, we need in the SRC/main build a the same package path, and then define the aidl file:

    // INoteManager.aidl
    package me.shouheng.advanced.aidl;
    import me.shouheng.advanced.aidl.Note;
    interface INoteManager {
        Note getNote(long id);
        void addNote(long id, String name);
    }

    // Note.aidl
    package me.shouheng.advanced.aidl;
    parcelable Note;
Copy the code

Notice that in the INoteManager file, we define the various methods required for the remote service. There are only two methods defined here, one to get the note with the specified ID and one to add a note record to the remote service.

Once this is defined, we can compile the project so that we have the INoteManager class file generated for us in the build directory. Later, we can use this file to generate classes and methods for remote communication. But before we use this interface, let’s take a look at what is generated:

package me.shouheng.advanced.aidl;

public interface INoteManager extends android.os.IInterface {

    // Hand over to the remote to implement the specific business logic
    public static abstract class Stub extends android.os.Binder implements me.shouheng.advanced.aidl.INoteManager {

        private static final java.lang.String DESCRIPTOR = "me.shouheng.advanced.aidl.INoteManager";

        public Stub(a) {
            this.attachInterface(this, DESCRIPTOR);
        }

        // Wrap the remote object with a proxy
        public static me.shouheng.advanced.aidl.INoteManager asInterface(android.os.IBinder obj) {
            if ((obj==null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if(((iin! =null)&&(iin instanceof me.shouheng.advanced.aidl.INoteManager))) {
                return ((me.shouheng.advanced.aidl.INoteManager)iin);
            }
            // Return the proxy object
            return new me.shouheng.advanced.aidl.INoteManager.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder(a) {
            return this;
        }

        // Where the data exchange is actually sent
        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_getNote: {
                    data.enforceInterface(DESCRIPTOR);
                    long _arg0;
                    _arg0 = data.readLong();
                    // Use the template method to implement the business
                    me.shouheng.advanced.aidl.Note _result = this.getNote(_arg0);
                    reply.writeNoException();
                    if((_result! =null)) {
                        reply.writeInt(1);
                        _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                    } else {
                        reply.writeInt(0);
                    }
                    return true;
                }
                case TRANSACTION_addNote: {
                    data.enforceInterface(DESCRIPTOR);
                    long _arg0;
                    _arg0 = data.readLong();
                    java.lang.String _arg1;
                    _arg1 = data.readString();
                    // Use the template method to implement the business
                    this.addNote(_arg0, _arg1);
                    reply.writeNoException();
                    return true; }}return super.onTransact(code, data, reply, flags);
        }

        // The proxy object, which wraps the remote object, calls the remote object internally to get the remote service information
        private static class Proxy implements me.shouheng.advanced.aidl.INoteManager {

            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder(a) {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor(a) {
                return DESCRIPTOR;
            }

            @Override
            public me.shouheng.advanced.aidl.Note getNote(long id) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                me.shouheng.advanced.aidl.Note _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeLong(id);
                    // Actually calls the remote object internally, implementing the business logic in another process
                    mRemote.transact(Stub.TRANSACTION_getNote, _data, _reply, 0);
                    _reply.readException();
                    if ((0! =_reply.readInt())) { _result = me.shouheng.advanced.aidl.Note.CREATOR.createFromParcel(_reply); }else {
                        _result = null; }}finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void addNote(long id, java.lang.String name) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeLong(id);
                    _data.writeString(name);
                    // Actually calls the remote object internally, implementing the business logic in another process
                    mRemote.transact(Stub.TRANSACTION_addNote, _data, _reply, 0);
                    _reply.readException();
                } finally{ _reply.recycle(); _data.recycle(); }}}// method id, which marks which method is currently called
        static final int TRANSACTION_getNote = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addNote = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }

    public me.shouheng.advanced.aidl.Note getNote(long id) throws android.os.RemoteException;

    public void addNote(long id, java.lang.String name) throws android.os.RemoteException;
}
Copy the code

If you just look at the generated code above, you may still have no idea what the generated classes actually do. Let’s illustrate the specific workflow of AIDL by using the classes generated above.

First, we define the remote service and implement the business logic in the service:

public class NoteService extends Service {

    private CopyOnWriteArrayList<Note> notes = new CopyOnWriteArrayList<>();

    // The current service is running in another process where the business logic is implemented
    private Binder binder = new INoteManager.Stub() {
        @Override
        public Note getNote(long id) {
            return Observable.fromIterable(notes).filter(note -> note.id == id).singleOrError().blockingGet();
        }

        @Override
        public void addNote(long id, String name) {
            notes.add(newNote(id, name)); }};@Override
    public void onCreate(a) {
        super.onCreate();
        notes.add(new Note(100."Note 100"));
        notes.add(new Note(101."Note 101"));
    }

    // Return binder, which clients can retrieve and call using connections
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        returnbinder; }}Copy the code

Here two records are created in the onCreate() method and an instance of inotemanager.stub is created and returned in the onBind() method. We then start the remote service in an Activity and try to get the note record with the specified ID from the service. As expected, it functions somewhat like a ContentProvider, which provides data to the caller.

The following is an implementation of the Activity. Here we start the service in the onCreate() method. Start the service with the instantiated ServiceConnection as a parameter. In the ServiceConnection method, we call the asInterface(IBinder) method of inotemanager.stub to convert the service to INoteManager. Then get the note record with the specified ID from it.

    // Create a service connection
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // Return the proxy object
            INoteManager noteManager = INoteManager.Stub.asInterface(service);
            try {
                // Use proxy objects
                Note note = noteManager.getNote(100);
                LogUtils.d(note);
            } catch(RemoteException e) { e.printStackTrace(); }}@Override
        public void onServiceDisconnected(ComponentName name) {}};@Override
    protected void doCreateView(Bundle savedInstanceState) {
        Intent intent = new Intent(this, NoteService.class);
        // Bind the service
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy(a) {
        super.onDestroy();
        // Unbind serviceunbindService(connection); }}Copy the code

As defined in the asInterface() method of inotemanager.stub, the incoming service is returned as an inotemanager.stub.proxy. What we actually call in the onServiceConnected() method is the proxy class’s getNote() method. The proxy class in turn calls the incoming mremote.transact () method in its getNote() method. Service is the binder we created in NoteService. That is, when we call the getNote() method from onServiceConnected(), we actually call the transact() method of inotemanager.stub.

Therefore, we can see from the above:

  1. It is as if another process’s method was called in the current process. The process of this call is throughBinderTo achieve.
  2. When callingINoteManager.Stubtransact()Method by passing in an integercodeTo identify the method to fire, which is the number of the method we mentioned above.

Thus, we can summarize the roles of the various parts in the use of AIDL above by using the following diagram:

That is, the client accesses the Binder driver through the Proxy, and the Binder driver calls the Stub, which calls our business logic. The Proxy and Stub are used to unify the interface functions, the Proxy is used to tell us what methods are available in the remote service, and the specific business logic is implemented by the Stub. Process communication with a Binder occurs between a Proxy and a Stub.

conclusion

This is how Binder works. If you have any questions, please feel free to comment.

The resources

Source code: Android-References