Binder mechanism in Android system undoubtedly position, System_server through Binder to achieve inter-process communication, so as to achieve the ability to manage and call a series of system services. This article explains the Binder mechanism in terms of AIDL. Let’s start with a few concepts:

IBinder, Binder, BinderProxy, IInterface

  1. IBinder: IBinder is defined as an interface that enables objects to communicate remotely. Binder subclasses IBinder. IBinder has predefined interfaces for IPC. The IBinder objects can be passed by Binder Drivder during IPC.
  2. Binder: Implement IBinder with IPC communication capability. In IPC communication, a Binder represents a Server’s local object;
  3. BinderProxy : BinderProxy is an internal class of Binder that also implements IBinder. Different from Binder, BinderProxy is a proxy of Binder objects on the Client side. The Client communicates with the Server indirectly through BinderProxy.
  4. IInterface: indicates the interface provided by the Server.

One more note: When a Service registers with AMS, it is passed and stored with AMS as a Binder object. When a Client needs to communicate with a Service, AMS finds that it is actually a BinderProxy, because multiple clients can communicate with the Service at the same time. Of course, if the Service and Client are in the same process, AMS returns a Binder object, so there is no need for IPC. Binder Drivder helps convert Binder and BinderProxy during IPC.

AIDL

Let’s go back to the Android Service example (2)

Now let’s take a look at the aiDL file generated by the system:

/* * This file is auto-generated. DO NOT MODIFY. * Original file: /Users/lebens/Development/WorkSpace/jxx_workspace/Server/app/src/main/aidl/jxx/com/server/aidl/IServerServiceInfo.aidl * /
package jxx.com.server.aidl;
public interface IServerServiceInfo extends android.os.IInterface
{
    /** Local-side IPC implementation stub class. */
    public static abstract class Stub extends android.os.Binder implements jxx.com.server.aidl.IServerServiceInfo
    {
        private static final java.lang.String DESCRIPTOR = "jxx.com.server.aidl.IServerServiceInfo";
        /** Construct the stub at attach it to the interface. */
        public Stub(a)
        {
            this.attachInterface(this, DESCRIPTOR);
        }
        /** * Cast an IBinder object into an jxx.com.server.aidl.IServerServiceInfo interface, * generating a proxy if needed. */
        public static jxx.com.server.aidl.IServerServiceInfo asInterface(android.os.IBinder obj)
        {
            if ((obj==null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if(((iin! =null)&&(iin instanceof jxx.com.server.aidl.IServerServiceInfo))) {
                return ((jxx.com.server.aidl.IServerServiceInfo)iin);
            }
            return new jxx.com.server.aidl.IServerServiceInfo.Stub.Proxy(obj);
        }
        @Override public android.os.IBinder asBinder(a)
        {
            return this;
        }
        @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
        {
            java.lang.String descriptor = DESCRIPTOR;
            switch (code)
            {
                case INTERFACE_TRANSACTION:
                {
                    reply.writeString(descriptor);
                    return true;
                }
                case TRANSACTION_getServerInfo:
                {
                    data.enforceInterface(descriptor);
                    jxx.com.server.aidl.ServerInfo _result = this.getServerInfo();
                    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_setServerInfo:
                {
                    data.enforceInterface(descriptor);
                    jxx.com.server.aidl.ServerInfo _arg0;
                    if ((0! =data.readInt())) { _arg0 = jxx.com.server.aidl.ServerInfo.CREATOR.createFromParcel(data); }else {
                        _arg0 = null;
                    }
                    this.setServerInfo(_arg0);
                    reply.writeNoException();
                    if((_arg0! =null)) {
                        reply.writeInt(1);
                        _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                    }
                    else {
                        reply.writeInt(0);
                    }
                    return true;
                }
                default:
                {
                    return super.onTransact(code, data, reply, flags); }}}private static class Proxy implements jxx.com.server.aidl.IServerServiceInfo
        {
            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 jxx.com.server.aidl.ServerInfo getServerInfo(a) throws android.os.RemoteException
            {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                jxx.com.server.aidl.ServerInfo _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getServerInfo, _data, _reply, 0);
                    _reply.readException();
                    if ((0! =_reply.readInt())) { _result = jxx.com.server.aidl.ServerInfo.CREATOR.createFromParcel(_reply); }else {
                        _result = null; }}finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
            @Override public void setServerInfo(jxx.com.server.aidl.ServerInfo serverinfo) throws android.os.RemoteException
            {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if((serverinfo! =null)) {
                        _data.writeInt(1);
                        serverinfo.writeToParcel(_data, 0);
                    }
                    else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_setServerInfo, _data, _reply, 0);
                    _reply.readException();
                    if ((0!=_reply.readInt())) {
                        serverinfo.readFromParcel(_reply);
                    }
                }
                finally{ _reply.recycle(); _data.recycle(); }}}static final int TRANSACTION_getServerInfo = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_setServerInfo = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }
    public jxx.com.server.aidl.ServerInfo getServerInfo(a) throws android.os.RemoteException;
    public void setServerInfo(jxx.com.server.aidl.ServerInfo serverinfo) throws android.os.RemoteException;
}
Copy the code
  1. IServerServiceInfo implements IInterface, indicating that IServerServiceInfo has the interface required for IPC communication. At the bottom we can also see the getServerInfo() and setServerInfo() methods defined;
  2. The Stub object is a static inner class of IServerServiceInfo that inherits Binder and implements IServerServiceInfo. This means stubs can be passed between processes through Binder drivers and have the interface required by IPC. AsBinder () actually returns this object in Service.
  3. Proxy object, this is another Proxy object, but it’s a BinderProxy. This object exists to represent the BinderProxy, which translates the operations of the Client into the BinderProxy to communicate with the Server.
  4. The difference between Proxy and Stub is that Stud is itself a Binder object and can be passed directly by Binder drivers, while Proxy only represents the IPC interface provided by Service and actually implements IPC using the internal Proxy mRemote.

Stub

A few more points about stubs:

  1. DESCRIPTOR : This is used to uniquely represent a Stub. When a Stub is instantiated, the owner object is stored in DESCRIPTOR, that is, the IInterface represented by the Stub. At the same time, the queryLocalInterface is the Stub returned if it is queried in the same process.
  2. TRANSACTION_getServerInfo and TRANSACTION_setServerInfo: Interface methods used to identify Client operations.
  3. Binder Driver returns a BinderProxy object. If the Binder Driver returns a BinderProxy object, create a Proxy to return to the Client.
  4. OnTransact: This method is analyzed with the Proxy object below.

Proxy

  1. MRemote: This is a BinderProxy object passed by the Binder Driver. Proxy IPC is realized by mRemote;
  2. Here we also see the interface methods we defined, which are provided to the Client to communicate with the Server;
  3. MRemote transact() : With the help of Binder drivers, this is eventually called into Stub onTransact().

onTransact()

Take getServerInfo() as an example to analyze the onTransact passing process. Our first Proxy implementation looks like this

    @Override public jxx.com.server.aidl.ServerInfo getServerInfo(a) throws android.os.RemoteException
    {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        jxx.com.server.aidl.ServerInfo _result;
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            mRemote.transact(IServerServiceInfo.Stub.TRANSACTION_getServerInfo, _data, _reply, 0);
            _reply.readException();
            if ((0! =_reply.readInt())) { _result = jxx.com.server.aidl.ServerInfo.CREATOR.createFromParcel(_reply); }else {
                _result = null; }}finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }
Copy the code
  1. _data and _reply are used to transmit parameters from the Client to the Server and return results from the Server to the Client respectively. The parameters in these two are to be serialized, after all, IPC.
  2. Start calling Server methods with mremote.transact;
  3. Read the data returned by the Server through _reply, deserialize the result, and return it;

As can be seen from the above analysis, mRemote is actually a BinderProxy object. Let’s take a look at this method

/** * Default implementation rewinds the parcels and calls onTransact. On * the remote side, transact calls into the binder to do the IPC. */
    public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply,
            int flags) throws RemoteException {
        if (false) Log.v("Binder"."Transact: " + code + " to " + this);

        if(data ! =null) {
            data.setDataPosition(0);
        }
        boolean r = onTransact(code, data, reply, flags);
        if(reply ! =null) {
            reply.setDataPosition(0);
        }
        return r;
    }
Copy the code

The final call is to the Binder onTransact(), which is Stub onTransact()

@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
        {
            java.lang.String descriptor = DESCRIPTOR;
            switch (code)
            {
                case INTERFACE_TRANSACTION:
                {
                    reply.writeString(descriptor);
                    return true;
                }
                case TRANSACTION_getServerInfo:
                {
                    data.enforceInterface(descriptor);
                    jxx.com.server.aidl.ServerInfo _result = this.getServerInfo();
                    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_setServerInfo:
                {
                    data.enforceInterface(descriptor);
                    jxx.com.server.aidl.ServerInfo _arg0;
                    if ((0! =data.readInt())) { _arg0 = jxx.com.server.aidl.ServerInfo.CREATOR.createFromParcel(data); }else {
                        _arg0 = null;
                    }
                    this.setServerInfo(_arg0);
                    reply.writeNoException();
                    if((_arg0! =null)) {
                        reply.writeInt(1);
                        _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                    }
                    else {
                        reply.writeInt(0);
                    }
                    return true;
                }
                default:
                {
                    return super.onTransact(code, data, reply, flags); }}Copy the code

The case TRANSACTION_getServerInfo operation is also very simple, which is to pass the result required by the Client back to the Client via reply

conclusion

Through the above code analysis, we can quickly implement AIDL IPC, let’s summarize:

  1. The Server needs to define the interface to the Client, that is, implement the IInterface, and provide a Binder for the Binder Driver to find the Server, which is DESCRIPTOR;
  2. Before communicating with the Server, the Client obtains a Binder or BinderProxy object and establishes contact with the Server through the Binder Driver.
  3. Binder Driver automatically converts BinderProxy to Binder during communication.

Through the analysis of this example, in fact, the AMS of the system manages various system services in the same way, and establishes communication through the two binders.

One other thing to note is that both binders or Binderproxies run in their own Binder pools, which require thread switching when communicating directly with binders