Android interprocess communication -- understand Binder through AIDL and write Binder service

Posted by lovely on Mon, 22 Jun 2020 06:28:49 +0200

Series of articles
Android interprocess communication (I) - Android multiprocess mode
Android interprocess communication (2) - understanding Binder's mechanism
Android interprocess communication (3) - understand Binder through AIDL and write Binder service

In the previous chapter, I have learned the communication principle of Binder. Here, I pass AIDL again. I will try again and write a Binder myself.

If you are not familiar with AIDL, you can refer to this article AIDL usage details and process callback

1, Basic use of AIDL

The above code is also used here. First, the task class TaskInfo needs to inherit the Parcelable interface and let as help you implement the method, as follows:

public class TaskInfo implements Parcelable {

    public int id;
    public String url;
    public int progress;

    public TaskInfo() {
    }
    protected TaskInfo(Parcel in) {
        id = in.readInt();
        url = in.readString();
        progress = in.readInt();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(id);
        dest.writeString(url);
        dest.writeInt(progress);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    public static final Creator<TaskInfo> CREATOR = new Creator<TaskInfo>() {
        @Override
        public TaskInfo createFromParcel(Parcel in) {
            return new TaskInfo(in);
        }

        @Override
        public TaskInfo[] newArray(int size) {
            return new TaskInfo[size];
        }
    };

    @Override
    public String toString() {
        return "TaskInfo{" +
                "id=" + id +
                ", url='" + url + '\'' +
                ", progress=" + progress +
                '}';
    }
}

And then there was TaskInfo.aidl

//Note that the TaskInfo must be in the com.example.ipcdemo  Next, I might as well remind you that I can't find it.
parcelable TaskInfo;

IRemoteService.aidl :

//Remember to import TaskInfo.aidl  's bag
import com.example.ipcdemo.TaskInfo;
interface IRemoteService {
    //Sum of two numbers
    int add(int num1,int num2);

    //Add a task
    TaskInfo addTask(in TaskInfo info);
}

Then build it, and you will find that it is generated under build IRemoteService.java

Next, take a look at the java of IRemoteService. The code is a little long. You can skip:

public interface IRemoteService extends android.os.IInterface
{

  /** Local-side IPC implementation stub class. */
  public static abstract class Stub extends android.os.Binder implements com.example.ipcdemo.IRemoteService
  {
    private static final java.lang.String DESCRIPTOR = "com.example.ipcdemo.IRemoteService";
    /** Construct the stub at attach it to the interface. */
    public Stub()
    {
      this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * Cast an IBinder object into an com.example.ipcdemo.IRemoteService interface,
     * generating a proxy if needed.
     */
    public static com.example.ipcdemo.IRemoteService asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.example.ipcdemo.IRemoteService))) {
        return ((com.example.ipcdemo.IRemoteService)iin);
      }
      return new com.example.ipcdemo.IRemoteService.Stub.Proxy(obj);
    }
    @Override public android.os.IBinder asBinder()
    {
      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_add:
        {
          data.enforceInterface(descriptor);
          int _arg0;
          _arg0 = data.readInt();
          int _arg1;
          _arg1 = data.readInt();
          int _result = this.add(_arg0, _arg1);
          reply.writeNoException();
          reply.writeInt(_result);
          return true;
        }
        case TRANSACTION_addTask:
        {
          data.enforceInterface(descriptor);
          com.example.ipcdemo.TaskInfo _arg0;
          if ((0!=data.readInt())) {
            _arg0 = com.example.ipcdemo.TaskInfo.CREATOR.createFromParcel(data);
          }
          else {
            _arg0 = null;
          }
          com.example.ipcdemo.TaskInfo _result = this.addTask(_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;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
    }
    private static class Proxy implements com.example.ipcdemo.IRemoteService
    {
      private android.os.IBinder mRemote;
      Proxy(android.os.IBinder remote)
      {
        mRemote = remote;
      }
      @Override public android.os.IBinder asBinder()
      {
        return mRemote;
      }
      public java.lang.String getInterfaceDescriptor()
      {
        return DESCRIPTOR;
      }
      //Sum of two numbers

      @Override public int add(int num1, int num2) throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        int _result;
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          _data.writeInt(num1);
          _data.writeInt(num2);
          boolean _status = mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            return getDefaultImpl().add(num1, num2);
          }
          _reply.readException();
          _result = _reply.readInt();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }
      //Add a task

      @Override public com.example.ipcdemo.TaskInfo addTask(com.example.ipcdemo.TaskInfo info) throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        com.example.ipcdemo.TaskInfo _result;
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          if ((info!=null)) {
            _data.writeInt(1);
            info.writeToParcel(_data, 0);
          }
          else {
            _data.writeInt(0);
          }
          boolean _status = mRemote.transact(Stub.TRANSACTION_addTask, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            return getDefaultImpl().addTask(info);
          }
          _reply.readException();
          if ((0!=_reply.readInt())) {
            _result = com.example.ipcdemo.TaskInfo.CREATOR.createFromParcel(_reply);
          }
          else {
            _result = null;
          }
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }
      public static com.example.ipcdemo.IRemoteService sDefaultImpl;
    }
    static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_addTask = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    public static boolean setDefaultImpl(com.example.ipcdemo.IRemoteService impl) {
      if (Stub.Proxy.sDefaultImpl == null && impl != null) {
        Stub.Proxy.sDefaultImpl = impl;
        return true;
      }
      return false;
    }
    public static com.example.ipcdemo.IRemoteService getDefaultImpl() {
      return Stub.Proxy.sDefaultImpl;
    }
  }

  /** Default implementation for IRemoteService. */
  public static class Default implements com.example.ipcdemo.IRemoteService
  {
    //Sum of two numbers

    @Override public int add(int num1, int num2) throws android.os.RemoteException
    {
      return 0;
    }
    //Add a task

    @Override public com.example.ipcdemo.TaskInfo addTask(com.example.ipcdemo.TaskInfo info) throws android.os.RemoteException
    {
      return null;
    }
    @Override
    public android.os.IBinder asBinder() {
      return null;
    }
  }
  //Sum of two numbers

  public int add(int num1, int num2) throws android.os.RemoteException;
  //Add a task

  public com.example.ipcdemo.TaskInfo addTask(com.example.ipcdemo.TaskInfo info) throws android.os.RemoteException;

Before you explain it, learn some concepts:

  • Iinterface: the interface that the Aidl file must inherit. It has only one method, IBinder asBinder(), to implement its class. It represents the ability to transfer Binder objects across processes, or Binder proxy objects. For example, in the above code, this is returned, indicating that the inner class Stub has the function of cross processes.
  • IBinder: the object that implements this interface has the ability of cross process book transmission. When the cross process data flows through the driver, the driver will recognize the Binder type data, so as to automatically complete the conversion of Binder local objects and Binder proxy objects in different processes.

2, Analyze Binder process

First, let's look at how we build Binder services when we are in services:

    //AIDL's services,
    IRemoteService.Stub mBinder = new IRemoteService.Stub() {
        @Override
        public int add(int num1, int num2) throws RemoteException {
            /**
             * Here is the specific implementation method, such as directly returning the sum of two numbers
             */
            Log.d(TAG, "zsr add: Two numbers passed by the client were received: "+num1+" "+num2);
            return (num1 + num2);
        }

        @Override
        public TaskInfo addTask(TaskInfo info) throws RemoteException {
            Log.d("zsr", "Receive information from client: "+info);
            info.id = 0;
            info.progress = 50;
           // mHandler.sendEmptyMessage(1);
           // mTaskInfo = info;
            return info;
        }
    };

As you can see, the sub class Stub of IRemoteService is used. Let's start with this class.

DESCRIPTOR
See that in the Stub subclass, a tag string is implemented, which is represented by package name plus type:

 private static final java.lang.String DESCRIPTOR = "com.example.ipcdemo.IRemoteService";

Then in its constructor, register the current Binder and string:

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

When you need to get the binder from ServerManager later, you need this string, for example:

queryLocalInterface(DESCRIPTOR)

asInterface
Convert the server-side Binder object into all AIDL objects on the client side; for example:

IRemoteService mBinder = IRemoteService.Stub.asInterface(service);

This transformation is divided into processes. If the same process, you can directly return to itself. If it is a cross process, you need to use its proxy class to transform. It can also be seen from the code:

    public static com.example.ipcdemo.IRemoteService asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.example.ipcdemo.IRemoteService))) {
        return ((com.example.ipcdemo.IRemoteService)iin);
      }
      return new com.example.ipcdemo.IRemoteService.Stub.Proxy(obj);
    }

asBinder
As we know from the above introduction, the returned object has the ability of cross process. Here, this is returned to represent Stub.

onTransact
This method runs in the Binder thread pool on the server side, where data writing and result reading are all done.

When the client initiates a cross process request, the remote request will be encapsulated at the bottom and then handed over to the secondary method for processing. From the code, you can know which method is currently executing, for example, we defined the index of add() and addTask()

    static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_addTask = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);

In this way, we can get the corresponding method by code:

Continue to see onTransact(int code, Parcel data, Parcel reply, int flags). Its parameters include the Parcel parameters that can be sequenced and deserialized, data and reply;
We can take out parameters from data (if there are parameters in the method), pay attention to the order, and then write the structure to replay, so when your method has a return value, replay will return what you need.
Note that onTransact needs to return true, and false means the request failed.

IRemoteService#Proxy
From the above to the above, when calling across processes, asInterface returns the strength of proxy class, which also implements IRemoteService interface Android interprocess communication (2) - understanding Binder's mechanism We know that the agent mode of Binder is that when A accesses the object method of B, B will not give the object to A, but will give it a specific agent class of the same method, and then A will pass the parameters to B through this agent, and B will calculate and then pass the results to A, so as to realize the role of cross process.

And it does:

First pass_ data write in all the parameters, and then use mRemote.transact The parameter is passed to the service. It calls the onTransact method, writes the result to reply through the thread pool, and finally returns it to Binder to complete the cross process action.

In this way, we have completed the analysis of AIDL

3, Custom AIDL service

From the above point of view, in AIDL, the logic generated by the system is messy, and all classes are integrated together. However, after we have made it clear, it is relatively easy to understand. So here, let's write one ourselves, instead of using the system generated one.

First, define an interface to integrate IInterface, add add and addTask methods, and define the id of the method:

public interface IRemote extends IInterface {
    static final java.lang.String DESCRIPTOR = "com.example.ipcdemo.aidl.IRemote";

    static final int TRANSACTION_add = (IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_addTask = (IBinder.FIRST_CALL_TRANSACTION + 1);

    //Sum of two numbers
    public int add(int num1, int num2) throws RemoteException;
    //Add a task
    public TaskInfo addTask(TaskInfo info) throws RemoteException;

}

Then create an implementation class IRemoteLmpl with cross process, integrate Binder and implement IRemote interface:

public class IRemoteImpl extends Binder implements IRemote {
    @Override
    public int add(int num1, int num2) throws RemoteException {
        return 0;
    }

    @Override
    public TaskInfo addTask(TaskInfo info) throws RemoteException {
        return null;
    }

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

Next, we should register the binder and string description into the service in the construction method of:

    public IRemoteImpl() {
        this.attachInterface(this,DESCRIPTOR);
    }

The method asInterface to implement the AIDL object transformed by the server for the client to call:

    public static IRemote asInterface(IBinder obj){
        if (obj == null) {
            return null;
        }
        if (obj instanceof IRemote){
            return (IRemote) obj;
        }
        return new Proxy(obj);
    }

The Proxy proxy class will be written later.

Next, write the specific implementation method onTransact of IRemote:

    @Override
    protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
        //Corresponding method by code
        switch (code) {
            case TRANSACTION_add:
                data.enforceInterface(DESCRIPTOR);
                //Parameter data received
                int num1 = data.readInt();
                int num2 = data.readInt();
                int result = this.add(num1, num2);
                reply.writeNoException();
                //Return the result to the return parameter
                reply.writeInt(result);
                return true;
            case TRANSACTION_addTask:
                data.enforceInterface(DESCRIPTOR);
                TaskInfo info = null;
                if (data.readInt() != 0) {
                    //Get data through deserialization
                    info = TaskInfo.CREATOR.createFromParcel(data);
                }
                TaskInfo resultInfo = this.addTask(info);
                reply.writeNoException();
                if ((resultInfo!=null)) {
                    reply.writeInt(1);
                    info.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                }
                else {
                    reply.writeInt(0);
                }
                return true;
            default:
                return super.onTransact(code, data, reply, flags);
        }
    }

There is no problem with the transformation of some data. Then, implement the proxy class to facilitate cross process calls:

 /**
     * Cross process proxy class
     */

    private static class Proxy implements IRemote {
        private IBinder mRemote;

        Proxy(IBinder iBinder) {
            mRemote = iBinder;
        }

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

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

        @Override
        public int add(int num1, int num2) throws RemoteException {
            Parcel data = Parcel.obtain();
            Parcel replay = Parcel.obtain();

            int reslut;
            try {
                data.writeInterfaceToken(DESCRIPTOR);
                data.writeInt(num1);
                data.writeInt(num2);
                /**
                 * As you can see, when a cross process is called, it is only to run the parameters of the caller to its own method, and then return the result back
                 */
                mRemote.transact(TRANSACTION_add, data, replay, 0);
                replay.readException();
                reslut = replay.readInt();
            } finally {
                data.recycle();
                replay.recycle();
            }


            return reslut;
        }

        @Override
        public TaskInfo addTask(TaskInfo info) throws RemoteException {
            Parcel data = Parcel.obtain();
            Parcel replay = Parcel.obtain();
            TaskInfo result;
            try {
                data.writeInterfaceToken(DESCRIPTOR);
                if ((info != null)) {
                    //Use data 0,1 to distinguish whether the write is successful
                    data.writeInt(1);
                    info.writeToParcel(data, 0);
                } else {
                    data.writeInt(0);
                }
                mRemote.transact(TRANSACTION_addTask, data, replay, 0);

                replay.readException();
                if ((0 != replay.readInt())) {
                    result = TaskInfo.CREATOR.createFromParcel(replay);
                } else {
                    result = null;
                }
            } finally {
                data.recycle();
                replay.recycle();
            }
            return result;
        }

    }

In this way, our code is finished

Next, modify the AIDL service for the RemoteService that is called by others:

    //AIDL's services,
    IRemoteImpl mBinder = new IRemoteImpl() {
        @Override
        public int add(int num1, int num2) throws RemoteException {
            /**
             * Here is the specific implementation method, such as directly returning the sum of two numbers
             */
            Log.d(TAG, "zsr add: Two numbers passed by the client were received: "+num1+" "+num2);
            return (num1 + num2);
        }

        @Override
        public TaskInfo addTask(TaskInfo info) throws RemoteException {
            Log.d("zsr", "Receive information from client: "+info);
            info.id = 0;
            info.progress = 50;
           // mHandler.sendEmptyMessage(1);
           // mTaskInfo = info;
            return info;
        }
    };

Then, copy the two methods to the client:

Bind server service:

//Bind AIDL service
intent.setClassName("com.example.ipcdemo","com.example.ipcdemo.service.RemoteService");
    class RemoteService implements ServiceConnection{

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
          //  mBinder = IRemoteService.Stub.asInterface(service);
            mBinder = IRemoteImpl.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    }

Then in the click event, call the AIDL method to get the data:

    public void testAidl(View view) {
        try {
            if (mBinder != null) {
                TaskInfo info = new TaskInfo();
                info.url = "www.google.com";
                TaskInfo taskInfo = mBinder.addTask(info);

                int num = mBinder.add(2,3);

                Log.d(TAG, "zsr testAidl: Get server data: "+taskInfo+" "+num);
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }

    }

Print as follows;

In this way, we can also achieve cross process communication without AIDL.

Project code: https://gitee.com/zhengshaorui/IpcDemo

Topics: Android Java Google