AIDL analysis of Android process communication

AIDL

As we mentioned earlier, when using AIDL to write IPC code, we only need to write a simple interface aidl file.


After Make Project, the system will help us generate Java files.

AIDL Makefile Analysis

AIDL helps us generate content:

As you can see, the generated interface is  IMyAidl inherited  IInterface. IInterface is a generic interface defined for inter-process communication.

It  IMyAidl also includes the two methods we declared in the aidl file.

In addition to that, IMyAidl there is an abstract class  Stub, which is  an abstract class, Binderthat implements the  IMyAidl interface:

Stub

public static abstract class Stub extends android.os.Binder implements net.sxkeji.shixinandroiddemo2.IMyAidl {
    //Unique identifier, usually the full path
        private static final java.lang.String DESCRIPTOR = "net.sxkeji.shixinandroiddemo2.IMyAidl";

    /**
     * Bind the current interface to Binder
     */
    public Stub() {
        this.attachInterface(this, DESCRIPTOR);
    }

    /**
     * Convert an IBinder to IMyAidl, create an agent if not in a process
     */
    public static net.sxkeji.shixinandroiddemo2.IMyAidl asInterface(android.os.IBinder obj) {
        if ((obj == null)) {
            return null;
        }
        / / Take the identity to query the interface from the local
        android.os.IInterface iin = obj.queryLocalInterface (DESCRIPTOR);
        if (((iin != null) && (iin instanceof net.sxkeji.shixinandroiddemo2.IMyAidl))) {
            return ((net.sxkeji.shixinandroiddemo2.IMyAidl) iin);
        }
        //Return to proxy if not found
        return new net.sxkeji.shixinandroiddemo2.IMyAidl.Stub.Proxy(obj);
    }

    //Override the method of IInterface to get the Binder object corresponding to the current interface
    @Override
    public android.os.IBinder asBinder() {
        return this;
    }

    //Key method, handle operation, return
    @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: { //Get the descriptor of the current interface
                reply.writeString(DESCRIPTOR);
                return true;
            }
            case TRANSACTION_addPerson: { //Execute the addPerson method
                data.enforceInterface(DESCRIPTOR);
                net.sxkeji.shixinandroiddemo2.bean.Person _arg0;
                if ((0 != data.readInt())) { //Deserialize incoming data
                    _arg0 = net.sxkeji.shixinandroiddemo2.bean.Person.CREATOR.createFromParcel(data);
                } else {
                    _arg0 = null;
                }
                //Call the addPerson method, the implementation of this method is on the server side
                this.addPerson(_arg0);
                reply.writeNoException();
                return true;
            }
            case TRANSACTION_getPersonList: {
                data.enforceInterface(DESCRIPTOR);
                java.util.List<net.sxkeji.shixinandroiddemo2.bean.Person> _result = this.getPersonList();
                reply.writeNoException();
                reply.writeTypedList(_result);
                return true;
            }
        }
        return super.onTransact(code, data, reply, flags);
    }

    // proxy to return when not in a process
    private static class Proxy implements net.sxkeji.shixinandroiddemo2.IMyAidl {...}

    //Two codes for the onTransact method, respectively identifying the operation to be performed
    static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_getPersonList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}

Stub Several key contents of the introduction:

  1. 构造函数 
    • attachInterface() method called 
    • Bind a descriptor, a specific IInterface to the current Binder, so that subsequent calls to queryLocalInterface can get this
    • You need to create one  DESCRIPTOR, usually the specific path name of the class, to uniquely represent this IInterface
  2. asInterface() 
    • will be  IBinder converted to  IMyAidl , which is used to return to the client
    • If it is not in a process, the client holds a proxy
  3. onTransact() 
    • Binder's key way of handling things
    • According to the incoming code, call different methods on the local/server side

It can be seen that the proxy is returned to the client in different processes.

Proxy

private static class Proxy implements net.sxkeji.shixinandroiddemo2.IMyAidl {
    private android.os.IBinder mRemote; //Remote IBinder of the agent

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

    //Get the Binder of the proxy
    @Override
    public android.os.IBinder asBinder() {
        return mRemote;
    }

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

    /**
     * As an agent, after processing the data, directly call the actual Binder to process it
     */
    @Override
    public void addPerson(net.sxkeji.shixinandroiddemo2.bean.Person person) 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 ((person != null)) {
                _data.writeInt(1);
                person.writeToParcel(_data, 0);
            } else {
                _data.writeInt(0);
            }
            //call remote
            mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);
            _reply.readException();
        } finally {
            _reply.recycle();
            _data.recycle();
        }
    }

    @Override
    public java.util.List<net.sxkeji.shixinandroiddemo2.bean.Person> getPersonList() throws android.os.RemoteException {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        java.util.List<net.sxkeji.shixinandroiddemo2.bean.Person> _result;
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            mRemote.transact(Stub.TRANSACTION_getPersonList, _data, _reply, 0);
            _reply.readException();
            _result = _reply.createTypedArrayList(net.sxkeji.shixinandroiddemo2.bean.Person.CREATOR);
        } finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }
}

Summary of AIDL-generated content

  • IInterface types of interfaces, including: 
    • Stub abstract class
    • Operation methods defined by aidl interface
  • Stub , is one  Binder, and is also one  IInterface, including: 
    • asInterface() The method of converting Binder to IInterface 
    • onTransact() Methods to handle scheduling 
    • onTransact() Two flags used  to identify the action to be taken in
    • IInterface type of proxy
  • ProxyIInterface types of proxies, including: 
    • The pseudo-implementation of the interface definition method, the actual call is the real Binder method

One sentence summary: AIDL helps us generate  Binder conversion classes with cross-platform interfaces  Stub, as well as proxies obtained by clients in different processes  Proxy.

Now go back and look at the use of AIDL, and you will understand a little more.

A review of the use of AIDL

Server

When using, first implement the classes in the AIDL generated file in the Service of another process  Stub , and then  onBind() return in:


Combined with the above analysis, we can know that what we instantiate on the server is  Stub the entity, it is  Binder both  IInterface. It implements the methods defined by the interface in it, and then  onBind() returns itself in it.

client

Use the binding service in the Activity  bindService() , and then call in the callback to  Stub.asInterface() convert the obtained remote Binder into the defined interface. If it is cross-process, what is actually obtained here is the proxy interface:


Then you can call the method in the Service.

summary

According to the above analysis, we can see that AIDL helps us do the following things:

  1. Generate interface classes that can be accessed by different processes according to the predetermined interface
  2. The common carrier of Binder and interface is provided in the interface class Stub
  3. Created a proxy class in  Stub the mapping call to the actual interface implementation

With AIDL, it becomes very simple for us to write cross-process operations, we only need to focus on the implementation of the business interface.

Manually write a Binder

We can imitate the files created by AIDL and manually write a Binder to deepen our understanding.

The first is to define a cross-process interface and implement IInterface

In it, define the operations to be done across processes, and the code that identifies these two operations:

public interface IMyAidlDiy extends IInterface {
    static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_getPersonList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);

    public void addPerson(Person person) throws RemoteException;

    public List<Person> getPersonList() throws RemoteException;
}

Then create this interface and the corresponding Binder conversion class in it Stub

Since it wants to please both ends, it needs to inherit  Binder and implement the interface defined earlier, and also provide methods for Binder and interface conversion, as well as methods for handling things as an interface:

public static abstract class Stub extends Binder implements IMyAidlDiy {

    private static final String DESCRIPTOR = "net.sxkeji.shixinandroiddemo2.activity.ipc.IMyAidlDiy";

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

    @Override
    public IBinder asBinder() {
        return this;
    }

    public static IMyAidlDiy asInterface(IBinder binder){
        if (binder == null){
            return null;
        }

        IInterface localInterface = binder.queryLocalInterface(DESCRIPTOR);
        if (localInterface != null && localInterface instanceof IMyAidlDiy){
            return (IMyAidlDiy) localInterface;
        }else {
            return new Stub.Proxy(localInterface);
        }
    }

    @Override
    protected boolean onTransact(final int code, final Parcel data, final Parcel reply, final int flags) throws RemoteException {
        switch (code){
            case TRANSACTION_addPerson:
                data.enforceInterface(DESCRIPTOR);
                Person _arg0;
                if (data.readInt() != 0){
                    _arg0 = Person.CREATOR.createFromParcel(data);  //反序列化参数
                }else {
                    _arg0 = null;
                }
                this.addPerson(_arg0);
                reply.writeNoException();
                return true;
            case TRANSACTION_getPersonList:
                data.enforceInterface(DESCRIPTOR);
                List<Person> personList = this.getPersonList();
                reply.writeNoException();
                reply.writeTypedList(personList);
                break;
        }
        return super.onTransact(code, data, reply, flags);
    }

}

最后创建代理接口,在不同进程中,客户端持有的是代理

它的作用就是伪装成真的 Binder,实际被调用时将数据处理成 Parcel,然后让被代理的 Binder 去处理:

private static class Proxy implements IMyAidlDiy {
    private IBinder mRemote;

    public Proxy(final IBinder obj) {
        mRemote = obj;
    }

    public java.lang.String getInterfaceDescriptor() {  //伪装的和真的 Binder 名一样
        return DESCRIPTOR;
    }

    @Override
    public void addPerson(final Person person) throws RemoteException {
        Parcel _data = Parcel.obtain();
        Parcel _reply = Parcel.obtain();

        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            if (person != null) {
                _data.writeInt(1);
                person.writeToParcel(_data, 0);
            } else {
                _data.writeInt(0);
            }
            mRemote.transact(TRANSACTION_addPerson, _data, _reply, 0);  //这里调用实际的实现
            _reply.readException();

        } finally {
            _data.recycle();
            _reply.recycle();
        }
    }

    @Override
    public List<Person> getPersonList() throws RemoteException {
        Parcel _data = Parcel.obtain();
        Parcel _reply = Parcel.obtain();
        List<Person> _result;
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            mRemote.transact(TRANSACTION_getPersonList, _data, _reply, 0);
            _reply.readException();
            _result = _reply.createTypedArrayList(Person.CREATOR);
        } finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }

    @Override
    public IBinder asBinder() {
        return mRemote;
    }
}

自己写完实现对整个 Binder 实现的跨进程调用流程是否理解更深了呢。总结下:


需要注意的是,客户端在发起远程请求时,当前线程会被挂起直到服务端返回,因此尽量不要在 UI 线程发起远程请求。

而在服务端,Binder 方法是运行在 Binder 线程池中的,因此可以直接使用同步的方式实现。


Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326635288&siteId=291194637