Five minutes to understand Binder cross-process mechanism and AIDL tools in Android

Preface

For Android development, there are a lot of technologies that must be advanced, including Binder technology. For understanding of Binder, the author initially only used AIDL tools to develop interfaces and generate service methods for Service, but did not know much about the internal implementation details.I read it several times from books or searches, every time I seem to understand it or not, and then it's too long to see that impression.So this latest comprehensive understanding of this technology, I decided to record my understanding, on the one hand to consolidate understanding, on the other hand to share with friends in need.

The premise that two objects can access each other directly is that they both exist in the same memory address space and cannot call each other directly if they are in two different processes.Binder is a cross-process communication method in Android, that is, IPC (Inter-process Communication).First, we learn to develop the first known points of knowledge, threads are the smallest dispatching unit of the CPU; processes are a running activity of programs in the computer about a data collection; they are the basic unit of the system for resource allocation and scheduling; processes can contain multiple threads.The theory is that from an objective perspective, an ordinary app is a process. It is obvious that there is no direct communication between the two apps. If necessary, then various ways of IPC should be used. Here we only talk about binder, or the service in Android, which is one of the four components of Android. Binder is mainly used in service.

Binder Main Module

Figure 1, Binder Framework
Binder is mainly divided into Binder server, Binder client and Binder driver.
First, the Binder server can be seen as an object of the Binder class that, when created, receives messages sent by the Binder driver, which are processed in the callback to the received message and executes the service code.This callback is the onTransact() method.
From the translation point of view, transact means processing. Here you can think that the binder drives this intermediary to send the client's requirements to the server, which can handle them according to the callback information.Now that there is a receipt, the client must send the data before the server can receive it.

Finally, if a Binder client wants to access a remote service and invoke a service interface, it must either obtain the corresponding mRemote reference of the remote service in the Binder object or think directly of it as getting a Binder.It then calls its transact() method to pass the functions and parameters that the client will call to the server, which is, of course, passed to the server through a Binder-driven mediation.
Since the Binder driver is located in the kernel space and is at the bottom level, we don't need to go too far to understand its principle. We just need to look at it as a black box. We only need to look at the interface input and output, as a messenger to deliver information to the server, and so on, we can dig deeper.Here is an overview of the character device that allows users to communicate with Binder Driver through open and ioctl file operation functions from the / dev/binder device file node. It is mainly responsible for the establishment of the Binder communication mechanism and the management of Binder reference counts and data package transfers.

Demo example

Here's an example of an aidl demo linked to binderdemo Where the app module in this project is the server side of the binder, with MediaService as the server and the added AIDL interface.Then the binderclient module is the binder client.

// IMediaService.aidl
package com.example.binderserver;
import com.example.binderserver.MediaInfo;

// Declare any non-default types here with import statements
interface IMediaService{
void startplay(in MediaInfo info);
void stop();
void search(String name);
}

The aidl source for MediaInfo is:

// MediaInfo.aidl
package com.example.binderserver;

// Declare any non-default types here with import statements

parcelable MediaInfo;

The data types supported in AIDL are:
1. Basic data types: int, long, char, boolean, double, etc.
2.String and CharSequence;
3.ArrayList, and each of its elements must be a supported data type;
4.HashMap, and each of its elements is a supported data type;
5.Parcelable, that is, all objects that implement the parcelable interface;
6.AIDL, that is, all AIDL interfaces themselves.
Therefore, according to Article 5, MediaInfo is not a type supported by other AIDL s, and the custom class must inherit parcelable.
parcelable is the most important way of Android serialization and is important in cross-process communication, in short, it is important.Readers who want to know more about it can refer to another article of mine: Read about serialization in Android in one article
Next, we continue to persuade both the client and the server to get a better overview of the framework, so here's the code appendix.
The code on the server side is as follows:

   public class MediaService extends Service {
    private static final String TAG = "MediaService";

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return (IBinder) mBinder;
    }

    private IMediaService mBinder = new IMediaService.Stub() {
        @Override
        public void startplay(MediaInfo info) throws RemoteException {
            Log.d(TAG, "startplay mediaInfo =" + info.toString());
        }

        @Override
        public void stop() throws RemoteException {
            Log.d(TAG, "service stop  ");
        }

        @Override
        public void search(String name) throws RemoteException {
            Log.d(TAG, "service search name=" + name);
        }
    };
}

The client code is as follows:

public class MainActivity extends Activity {
    private static final String TAG = "binderclient";
    ServiceConnection conn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        conn = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
                Log.d(TAG, "onServiceConnected");
                IMediaService binder = IMediaService.Stub.asInterface(iBinder);
                try {
                    Log.d(TAG, "onServiceConnected search invoke");
                    binder.search("Forget Love Potion");
                    Log.d(TAG, "onServiceConnected startplay invoke");
                    binder.startplay(new MediaInfo("The brightest star in the night sky", "xxx", "escape plan"));
                    Log.d(TAG, "onServiceConnected stop invoke");
                    binder.stop();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onServiceDisconnected(ComponentName componentName) {
                Log.e(TAG, "onServiceDisconnected");
            }
        };

        Intent intent = new Intent();
        intent.setAction("example.bindertest");
        intent.setPackage("com.example.binderserver");
        bindService(intent, conn, BIND_AUTO_CREATE);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        unbindService(conn);
    }

}

The source code is attached, and since AIDL is a Binder tool provided by Android, it is not the real source code. If you download the github source code and compile it successfully, the IMediaService.java source, public interface IMediaService extends and roid.os.IInterface, will be found in the app/build/generated/aidl_source_output_dir/debug/compileDebugAidl/out/com/example/binderserver directory.You can see that AIDL's methods are classes of interface, and compilation naturally generates interface classes, because it inherits IInterface, because this is an interface class
public interface IInterface {
IBinder asBinder();
}
So you also need to implement its asBinder method.

/*
 * This file is auto-generated.  DO NOT MODIFY.
 */
package com.example.binderserver;
// Declare any non-default types here with import statements

public interface IMediaService extends android.os.IInterface
{
  /** Default implementation for IMediaService. */
  public static class Default implements com.example.binderserver.IMediaService
  {
    @Override public void startplay(com.example.binderserver.MediaInfo info) throws android.os.RemoteException
    {
    }
    @Override public void stop() throws android.os.RemoteException
    {
    }
    @Override public void search(java.lang.String name) throws android.os.RemoteException
    {
    }
    @Override
    public android.os.IBinder asBinder() {
      return null;
    }
  }
  /** Local-side IPC implementation stub class. */
  public static abstract class Stub extends android.os.Binder implements com.example.binderserver.IMediaService
  {
    private static final java.lang.String DESCRIPTOR = "com.example.binderserver.IMediaService";
    /** Construct the stub at attach it to the interface. */
    public Stub()
    {
      this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * Cast an IBinder object into an com.example.binderserver.IMediaService interface,
     * generating a proxy if needed.
     */
    public static com.example.binderserver.IMediaService asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.example.binderserver.IMediaService))) {
        return ((com.example.binderserver.IMediaService)iin);
      }
      return new com.example.binderserver.IMediaService.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_startplay:
        {
          data.enforceInterface(descriptor);
          com.example.binderserver.MediaInfo _arg0;
          if ((0!=data.readInt())) {
            _arg0 = com.example.binderserver.MediaInfo.CREATOR.createFromParcel(data);
          }
          else {
            _arg0 = null;
          }
          this.startplay(_arg0);
          reply.writeNoException();
          return true;
        }
        case TRANSACTION_stop:
        {
          data.enforceInterface(descriptor);
          this.stop();
          reply.writeNoException();
          return true;
        }
        case TRANSACTION_search:
        {
          data.enforceInterface(descriptor);
          java.lang.String _arg0;
          _arg0 = data.readString();
          this.search(_arg0);
          reply.writeNoException();
          return true;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
    }
    private static class Proxy implements com.example.binderserver.IMediaService
    {
      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;
      }
      @Override public void startplay(com.example.binderserver.MediaInfo info) 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 ((info!=null)) {
            _data.writeInt(1);
            info.writeToParcel(_data, 0);
          }
          else {
            _data.writeInt(0);
          }
          boolean _status = mRemote.transact(Stub.TRANSACTION_startplay, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            getDefaultImpl().startplay(info);
            return;
          }
          _reply.readException();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
      }
      @Override public void stop() throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          boolean _status = mRemote.transact(Stub.TRANSACTION_stop, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            getDefaultImpl().stop();
            return;
          }
          _reply.readException();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
      }
      @Override public void search(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.writeString(name);
          boolean _status = mRemote.transact(Stub.TRANSACTION_search, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            getDefaultImpl().search(name);
            return;
          }
          _reply.readException();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
      }
      public static com.example.binderserver.IMediaService sDefaultImpl;
    }
    static final int TRANSACTION_startplay = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_stop = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    static final int TRANSACTION_search = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
    public static boolean setDefaultImpl(com.example.binderserver.IMediaService impl) {
      if (Stub.Proxy.sDefaultImpl == null && impl != null) {
        Stub.Proxy.sDefaultImpl = impl;
        return true;
      }
      return false;
    }
    public static com.example.binderserver.IMediaService getDefaultImpl() {
      return Stub.Proxy.sDefaultImpl;
    }
  }
  public void startplay(com.example.binderserver.MediaInfo info) throws android.os.RemoteException;
  public void stop() throws android.os.RemoteException;
  public void search(java.lang.String name) throws android.os.RemoteException;
}

Method Meaning

Importantly, to understand the use of Binder and AIDL, the following function definitions must be remembered, just like a knowledge dictionary. If you don't understand the source code, look up the dictionary. If you remember the following points, you must have a basic understanding of Binder.

  1. DESCRIPTOR: The unique identifier of a Binder, usually represented by the class name of the current Binder.Here's "com.example.binderserver.IMediaService"
  2. Stub: is an abstract class, which inherits the Binder class and implements the IMediaService interface, mainly on the server side.Now that we inherit Binder, we can think of Stub as a Binder.It is defined as an abstract class because specific service functions need to be implemented by a programmer.Look at the IMediaService source code generated by demo compilation. public static abstract class Stub extends android.os.Binder implements com.example.binderserver.IMediaService Since IMediaService is an interface class, the Stub class implement IMediaService class needs to implement its interfaces, and the server side needs programmers to implement these interfaces manually.
 public void startplay(com.example.binderserver.MediaInfo info) throws android.os.RemoteException;
  public void stop() throws android.os.RemoteException;
  public void search(java.lang.String name) throws android.os.RemoteException;

The onTransact method is then overloaded inside the Stub class.Since the inherited Binder class, public class Binder implements IBinder, inherits the interface public interface IBinder, the service-side function Stub implements the Binder interface onTransact and the requests it brings with it, as shown in Article 5 below.Here onTransact handles a client request from Binder and then calls the interface function of IMediaService, the part that needs to be implemented by the programmer, and returns.
4. asInterface(android.os.IBinder obj): Converts a server-side Binder object to an AIDL interface type object that the client needs. This transformation distinguishes processes. If the client and the server are in the same process, this method returns the server-side object itself without an IPC call, otherwise it returns a system-encapsulated Stub.proxy object.You can see that in the source code of the asInterface of the Stub class, there is android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); this is used to determine if it is a local interface, the method of this process, and then if iin is not empty and an instance of IMediaService, it is returned directly.Otherwise, call the Proxy method of Stub.As shown in the following figure.

/**
     * Cast an IBinder object into an com.example.binderserver.IMediaService interface,
     * generating a proxy if needed.
     */
    public static com.example.binderserver.IMediaService asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.example.binderserver.IMediaService))) {
        return ((com.example.binderserver.IMediaService)iin);
      }
      return new com.example.binderserver.IMediaService.Stub.Proxy(obj);
    }
  1. asBinder: Returns the current Binder object.
  2. OnTransact: Runs in the service-side Binder thread pool, where data requests from the client are handled by a callback to this method after encapsulation at the bottom of the system, where the public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) parameter code represents the method value of the client call, data is the parameter of the client call method, and reply is the client call methodReturn value of.Since the data wrapped in the data is written in the client's transact method, its order is defined by the AIDL tool, naturally in onTransact, the AIDL tool also unwraps according to the defined package wrapping order for processing.
  3. enforceInterface: For some kind of verification, corresponds to the writeInterfaceToken in the client Proxy.
  4. Proxy: Running on the client, as a proxy for the client to access the server, the client creates the Parcel object_data and_reply in the corresponding interface functions. To get a better understanding of Parcel, you can also see another of my interpretations of serialization, which are similar. Read about serialization in Android in one article The parameter information encapsulated in serialization is also agreed in order.
		android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();

Write the parameter information required by the method to be invoked into _data, then encapsulate the code and _data,_reply, (_data is the package to be passed to the remote Binder service, where the code corresponding parameter is put in, and then make the cross-process call; at the same time, the client's current thread hangs, waiting for the onTransact party on the service sideAfter the method call, the execution result is put into _reply, and the server sends a notify message to the Binder driver so that the client thread can return from the Binder driver to continue executing the client code.
The method code here is then used to identify which function the client wants to call on the server side, that is, a set of int values agreed upon by both parties representing different server side functions.The code of the client transact and the code of the server onTransact correspond.

static final int TRANSACTION_startplay = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_stop = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_search = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);

test result

Readers can download demo source code to test themselves as needed at binderdemo From the explanations in the above sections, the reader can also test whether or not it is in the same process
Printing of client execution results

2020-02-06 16:59:39.827 7447-7447/com.example.binderclient D/binderclient: onServiceConnected
2020-02-06 16:59:39.828 7447-7447/com.example.binderclient D/binderclient: onServiceConnected search invoke
2020-02-06 16:59:39.829 7447-7447/com.example.binderclient D/binderclient: onServiceConnected startplay invoke
2020-02-06 16:59:39.829 7447-7447/com.example.binderclient D/binderclient: onServiceConnected stop invoke

Simultaneous server-side execution results:

2020-02-06 16:59:39.828 6519-6532/com.example.binderserver D/MediaService: service search name=Forget Love Potion
2020-02-06 16:59:39.829 6519-6531/com.example.binderserver D/MediaService: startplay mediaInfo =com.example.binderserver.MediaInfo@54956ae
2020-02-06 16:59:39.829 6519-7005/com.example.binderserver D/MediaService: service stop  

epilogue

This is the end of the article. I hope you can have a good understanding of Binder and AIDL. Readers who have used AIDL should be more impressed, but I recommend collecting it and reviewing it over time, otherwise they will forget it when they are not used often.For beginners, if you can't quickly understand, you can first remember the method meaning of the above chapters, and then demo them. If you can't remember, it's easy to pick up watermelon, lose sesame, and have hilly breasts.In this way, when practicing or practicing, you can understand by following Tusuoji.
Of course, this article only briefly explains some of the methods and principles through examples. There are other examples in Android, such as ServiceManager of Android system, which is the manager of all native Services in the system, and it is also a BinderServer. Students who want to continue in-depth research can continue to study its source code, and you can refer to the source code of the system: How to view Android system source code
Like or appreciate the help of your helpful classmates. The text refers to the knowledge in the book and your own understanding. If there are any errors, you are welcome to leave a message referring to the orthogonal flow.

Reference:
Exploration of the Art of Android Development
Android Core Analysis
Android Source Design Mode Analysis and Practice

17 original articles published. 2. 10,000 visits+
Private letter follow

Tags: Android Java github

Posted on Thu, 06 Feb 2020 23:34:27 -0500 by appels