Android cross process communication Binder workflow

Posted by Teck on Sun, 02 Jan 2022 21:51:05 +0100

Android cross process communication Binder workflow

This article only describes Binder's workflow, not Binder's principle

  1. First, what is AIDL

    AIDL is the Android Interface definition language

  2. AIDL and Binder action

    AIDL is based on the Binder mechanism. Using AIDL, the SDK can automatically generate the corresponding Binder classes for us.
    Binder is a bridge for communication between different processes.

  3. Get Binder implementation class using AIDL

    • Create an AIDL file using Android Studio

      new->AIDL

    • The instance name is ibookmanager aidl

      Custom types in Aidl need to be imported using import, and custom classes need to implement serialization interfaces (serializable or Parcelable) and be declared using a separate Aidl file with the same name, such as the Book used in the following example

      package com.example.ipctest.aidl;
      import com.example.ipctest.aidl.Book;
      import com.example.ipctest.aidl.IOnNewBookArrivedListener;
      // Declare any non-default types here with import statements
      
      interface IBookManager {
          List<Book> getBookList(); //Get books
          void addBook(in Book book); //Add books
      }
      
      //Book.aidl
      package com.example.ipctest.aidl;
      
      // Declare any non-default types here with import statements
      
      parcelable Book;
      
      //Book.java
      public class Book implements Parcelable {
      
          public int bookId;
          public String bookName;
      
          public Book(int bookId, String bookName) {
              this.bookId = bookId;
              this.bookName = bookName;
          }
      
          protected Book(Parcel in) {
              bookId = in.readInt();
              bookName = in.readString();
          }
      
          public static final Creator<Book> CREATOR = new Creator<Book>() {
              @Override
              public Book createFromParcel(Parcel in) {
                  return new Book(in);
              }
      
              @Override
              public Book[] newArray(int size) {
                  return new Book[size];
              }
          };
      
          @Override
          public int describeContents() {
              return 0;
          }
      
          @Override
          public void writeToParcel(Parcel dest, int flags) {
              dest.writeInt(bookId);
              dest.writeString(bookName);
          }
      }
      
    • You can build a project in build - > generated - > Aidl_ source_ output_ Find the automatically generated IBookManager interface in dir - > debug directory

      The main components of this interface

      • A static class Default implemented by Default
      • A static abstract class Stub inherits Binder and implements this interface. This is Binder. At the same time, it has a static internal class Proxy. You can think of Stub as a server and Proxy as a client.
      • The method previously declared in AIDL and the code value corresponding to the method
      //All interfaces that can be transmitted in the binder need to inherit IInterface
      public interface IBookManager extends android.os.IInterface
      {
        /** IBookManager Interface */
        public static class Default implements IBookManager
        {
          @Override public java.util.List<Book> getBookList() throws android.os.RemoteException
          {
            return null;
          }
          @Override public void addBook(Book book) throws android.os.RemoteException
          {
          }
          @Override
          public android.os.IBinder asBinder() {
            return null;
          }
        }
          
        //This is equivalent to a binder, which inherits from the binder class and implements this interface
        public static abstract class Stub extends android.os.Binder implements IBookManager
        {
          //The unique identifier of the binder is generally identified by the class name of the current binder
          private static final String DESCRIPTOR = "com.example.ipctest.aidl.IBookManager";
          // Default constructor, attaching Stub to interface 
          public Stub()
          {
            this.attachInterface(this, DESCRIPTOR);
          }
          /**
          Static method
           * Convert the Binder object of the server into the AIDL interface type object required by the client. This conversion process is process specific. If the client and the server belong to the same process
           * ,Then this method returns the server Stub object itself, otherwise it returns the system encapsulated Stub Proxy object
           * After obtaining the converted object, you can execute the required method
           */
          public static IBookManager asInterface(android.os.IBinder obj)
          {
            if ((obj==null)) {
              return null;
            }
            //The query implements the local instance of the Binder object. If it is null, it means that the client and the server do not belong to the same process, so you need to create a proxy object and return it to the client
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin!=null)&&(iin instanceof IBookManager))) {
              return ((IBookManager)iin);
            }
            //The client and server do not use proxy to wrap and return Binder in the same process. Proxy also implements IBookManager interface
            return new IBookManager.Stub.Proxy(obj);
          }
          //Returns the current binder object
          @Override public android.os.IBinder asBinder()
          {
            return this;
          }
          //This method runs in the Binder thread pool. When the client initiates a cross process request, the remote request will be encapsulated at the bottom of the system and handed over to this method for processing
          //When calling a remote service method, first execute the corresponding method in the Proxy, execute the onTransact() method of the object inheriting the Stub class through transact(), and call back the corresponding interface according to the code value in the onTransact method. At this time, the whole method execution process is roughly ended
          @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
          {
            String descriptor = DESCRIPTOR;
            switch (code)
            {
              case INTERFACE_TRANSACTION:
              {
                reply.writeString(descriptor);
                return true;
              }
              case TRANSACTION_basicTypes:
              {
                data.enforceInterface(descriptor);
                int _arg0;
                _arg0 = data.readInt();
                long _arg1;
                _arg1 = data.readLong();
                boolean _arg2;
                _arg2 = (0!=data.readInt());
                float _arg3;
                _arg3 = data.readFloat();
                double _arg4;
                _arg4 = data.readDouble();
                String _arg5;
                _arg5 = data.readString();
                this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
                reply.writeNoException();
                return true;
              }
              case TRANSACTION_getBookList: 
              {
                data.enforceInterface(descriptor);
                //Execute the getBookList() method implemented on the server side
                java.util.List<Book> _result = this.getBookList();
                reply.writeNoException();
                reply.writeTypedList(_result);
                return true;
              }
              case TRANSACTION_addBook:
              {
                data.enforceInterface(descriptor);
                Book _arg0;
                if ((0!=data.readInt())) {
                  _arg0 = Book.CREATOR.createFromParcel(data);
                }
                else {
                  _arg0 = null;
                }
                  //Execute the addBook() method implemented on the server
                this.addBook(_arg0);
                reply.writeNoException();
                return true;
              }
              default:
              {
                return super.onTransact(code, data, reply, flags);
              }
            }
          }
          //The static inner class Proxy of Stub is equivalent to the client
          private static class Proxy implements IBookManager
          {
            // Store the reference of the binder, which is used to call the method of the server
            private android.os.IBinder mRemote;
            Proxy(android.os.IBinder remote)
            {
              mRemote = remote;
            }
            @Override public android.os.IBinder asBinder()
            {
              return mRemote;
            }
            public String getInterfaceDescriptor()
            {
              return DESCRIPTOR;
            }
            //Implementation of getBookList()
            @Override public java.util.List<Book> getBookList() throws android.os.RemoteException
            {
               //data is the parameter to be passed. reply stores the return value and result stores the result
              android.os.Parcel _data = android.os.Parcel.obtain();
              android.os.Parcel _reply = android.os.Parcel.obtain();
              java.util.List<Book> _result;
              try {
                _data.writeInterfaceToken(DESCRIPTOR);
                //Let's start with the operation of cross process communication to see if it can succeed
                boolean _status = mRemote.transact(IBookManager.Stub.TRANSACTION_getBookList, _data, _reply, 0);
                //Cross process acquisition failure indicates that the client and server directly call the getbooklist method of stub in one process
                if (!_status && getDefaultImpl() != null) {
                  return getDefaultImpl().getBookList();
                }
                _reply.readException();
                _result = _reply.createTypedArrayList(Book.CREATOR);
              }
              finally { //Recycling resources
                _reply.recycle();
                _data.recycle();
              }
              return _result;
            }
            @Override public void addBook(Book book) 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 ((book!=null)) {
                  _data.writeInt(1);
                  book.writeToParcel(_data, 0);
                }
                else {
                  _data.writeInt(0);
                }
                boolean _status = mRemote.transact(IBookManager.Stub.TRANSACTION_addBook, _data, _reply, 0);
                if (!_status && getDefaultImpl() != null) {
                  getDefaultImpl().addBook(book);
                  return;
                }
                _reply.readException();
              }
              finally {
                _reply.recycle();
                _data.recycle();
              }
            }
            public static IBookManager sDefaultImpl;
          }
          //Declare two integer IDs to id entify the custom method
          static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
          static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
          public static boolean setDefaultImpl(IBookManager impl) {
            // Only one user of this interface can use this function
            // at a time. This is a heuristic to detect if two different
            // users in the same process use this function.
            if (Proxy.sDefaultImpl != null) {
              throw new IllegalStateException("setDefaultImpl() called twice");
            }
            if (impl != null) {
              Proxy.sDefaultImpl = impl;
              return true;
            }
            return false;
          }
          public static IBookManager getDefaultImpl() {
            return Proxy.sDefaultImpl;
          }
        }
        //Methods declared in IBookManager interface
        public java.util.List<Book> getBookList() throws android.os.RemoteException;
        public void addBook(Book book) throws android.os.RemoteException;
      }
      
  4. Workflow

    1. The client (an Activity) gets the Binder object passed from the server (a Service component) through bindService(), and calls the IBookManager.Stub.asInterface() method to convert it into the IBookManager type object required by the client (if it is not in the same process, the Binder is wrapped with the Proxy class and the object is returned). The method of the server can be called through this object
    2. The server needs to implement the onbind method and return the binder object. The class to which this binder object belongs must inherit IBookManager Stub class and implement the methods declared in IBookManager. (because stub is an abstract class that implements the IBookManager interface, subclasses must implement the methods declared by the IBookManager interface.)
    3. When the client calls a method of the server, such as getBookList(), after calling this method, if the client and server are not in the same process, immediately call the corresponding method getBookList() in the Proxy, and then in this method, call the transact() method of the binder to execute the onTransaction() method in the Stub, In this method, you will call the getBookList() method you implemented on the server, then return the results in turn, and the execution ends

    be careful:

    • The methods in the binder are executed in the binder thread pool, so do not start the thread again in the implementation interface method of the server, unless you are sure that you need to do so.
    • If the remote service method is a time-consuming method, the thread needs to be started when the client calls, otherwise ANR will be triggered

Reference books: < < Android development art exploration > >

Topics: Java Android