English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Detailed Explanation of Android AIDL - Process Communication Mechanism

Android AIDL, Android inter-process communication mechanism, here we will organize the knowledge of AIDL, to help everyone learn and understand this part of knowledge!

What is AIDL

AIDL, short for Android Interface Definition Language, is also known as Android Interface Description Language. It sounds very sophisticated, but in essence, it is just an auxiliary tool for generating inter-process communication interfaces. Its existence is in the form of a .aidl file, where developers need to define the interfaces for inter-process communication in this file. When compiling, the IDE will generate .java files that can be used in the project based on our .aidl interface files, which is somewhat similar to what we call 'syntactic sugar'.

The syntax of AIDL is the same as Java syntax, with a slight difference in importing packages. In Java, if two classes are in the same package, no import operation is required, but in AIDL, an import declaration must be made.

 AIDL In-depth

Imagine a scenario: we have a library management system, which is implemented through the CS mode. Specific management functions are implemented by the server process, and the client only needs to call the corresponding interfaces.

Then first define the ADIL interface of this management system.

We are /rc new aidl package, which contains three files: Book.java, Book.aidl, and IBookManager.aidl.

package com.example.aidl book
public class Book implements Parcelable {
 int bookId;
 String bookName;
 public Book(int bookId, String bookName) {
   this.bookId = bookId;
   this.bookName = bookName;
 }
 ...
}

package com.example.aidl;

Parcelable Book;

package com.example.aidl;
import com.example.aidl.Book;
inteface IBookManager {
  List<Book> getBookList();
  void addBook(in Book book);
}

The following section will explain these three files separately:

Book.java is the entity class we define, which implements the Parcelable interface, so that the Book class can be transmitted between processes.
Book.aidl is the declaration of this entity class in AIDL.
IBookManager is the interface for communication between the server and the client. (Note that in AIDL interfaces, in addition to basic types, a direction must be added before the parameter, where 'in' indicates input type parameters, 'out' indicates output type parameters, and 'inout' indicates input/output type parameters.)

After the compiler compiles, Android Studio automatically generates a .java file for our project, which contains three classes: IBookManager, Stub, and Proxy. These three classes are of static type, and we can completely separate them. The definitions of the three classes are as follows:

IBookManager

public interface IBookManager extends android.os.IInterface {
  public void addBook(net.bingyan.library.Book book) throws android.os.RemoteException;
  public java.util.List<net.bingyan.library.Book> getBookList() throws android.os.RemoteException;
}

Stub

public static abstract class Stub extends android.os.Binder implements net.bingyan.library.IBookManager {
    private static final java.lang.String DESCRIPTOR = "net.bingyan.library.IBookManager";
    static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1;
    /**
     * Construct the stub at attach it to the interface.
     */
    public Stub() {
      this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * Cast an IBinder object into an net.bingyan.library.IBookManager interface,
     * generating a proxy if needed. http://www.manongjc.com/article/1501.html
     */
    public static net.bingyan.library.IBookManager asInterface(android.os.IBinder obj) {
      if ((obj == null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin != null) && (iin instanceof net.bingyan.library.IBookManager))) {
        return ((net.bingyan.library.IBookManager) iin);
      }
      return new net.bingyan.library.IBookManager.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 {
      switch (code) {
        case INTERFACE_TRANSACTION: {
          reply.writeString(DESCRIPTOR);
          return true;
        }
        case TRANSACTION_addBook: {
          data.enforceInterface(DESCRIPTOR);
          net.bingyan.library.Book _arg0;
          if ((0 != data.readInt())) {
            _arg0 = net.bingyan.library.Book.CREATOR.createFromParcel(data);
          } else {
            _arg0 = null;
          }
          this.addBook(_arg0);
          reply.writeNoException();
          return true;
        }
        case TRANSACTION_getBookList: {
          data.enforceInterface(DESCRIPTOR);
          java.util.List<net.bingyan.library.Book> _result = this.getBookList();
          reply.writeNoException();
          reply.writeTypedList(_result);
          return true;
        }
      }
      return super.onTransact(code, data, reply, flags);
    }
}

Proxy

private static class Proxy implements net.bingyan.library.IBookManager {
      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;
      }
      /**
       * Demonstrates some basic types that you can use as parameters
       * and return values in AIDL. http://www.manongjc.com/article/1500.html
       */
      @Override
      public void addBook(net.bingyan.library.Book book) throws android.os.RemoteException {
        android.os.Parcel _reply = android.os.Parcel.obtain();
        java.util.List<net.bingyan.library.Book> _result;
        try {
          mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
          _data.writeInt(
            book.writeToParcel(_data, 0);1;
            _data.writeInt(0);
          } else {
            mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
          }
          public java.util.List<net.bingyan.library.Book> getBookList() throws android.os.RemoteException {
          _result = _reply.createTypedArrayList(net.bingyan.library.Book.CREATOR);
        }
          _reply.recycle();
          _data.recycle();
        }
      }
      @Override
      android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        java.util.List<net.bingyan.library.Book> _result;
        _data.writeInterfaceToken(DESCRIPTOR);
        try {
          mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
          _reply.readException();
          _result = _reply.createTypedArrayList(net.bingyan.library.Book.CREATOR);
          finally {
        }
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }
    }

The following is a description of the three classes generated:

  1. The IBookManager class is the interface we define, and Android Studio adds a superclass to it, making it inherit from the android.os.interface interface. This interface has only one method, IBinder asBinder(), so there are three methods with implementation in IBookManager. It is the communication window between the server process and the client process.
  2. Stub is an abstract class, which inherits from the android.os.Binder class and implements the IBookManager interface. In Stub, the asBinder() interface method has been implemented, and there are two AIDL interface methods defined by us for the subclasses to implement. It is used on the server side, so the server needs to implement these two methods.
  3. As the name suggests, Proxy is an agent class, which is an agent of the server side on the client side. It also implements the IBookManager interface and all the methods in IBookManager. It is used on the client side, acting as the server's agent on the client side. Now let's analyze these three classes one by one:
  4. The IBookManager class has nothing much to say; it simply inherits the asInterface interface, which is used to convert IBookManager to IBinder.
  5. The Proxy class has been mentioned before, it is a封装 class of inter-process communication mechanism, its internal implementation mechanism is Binder, and it is easy to see from the constructor. Its constructor accepts an IBinder type parameter, named remote, which obviously represents the server side. Let's take a look at the methods addBook() and getBookList() in this class:
@Override
public void addBook(net.bingyan.library.Book book) throws android.os.RemoteException {
   android.os.Parcel _reply = android.os.Parcel.obtain();
   java.util.List<net.bingyan.library.Book> _result;
   try {
      if ((book != null)) {
      _data.writeInt(
        book.writeToParcel(_data, 0);1;
        _data.writeInt(0);
      } else {
        mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
      }
      public java.util.List<net.bingyan.library.Book> getBookList() throws android.os.RemoteException {
      _result = _reply.createTypedArrayList(net.bingyan.library.Book.CREATOR);
    }
      _reply.recycle();
      _data.recycle();
    }
}
@Override /* http://www.manongjc.com/article/1547.html */
android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    java.util.List<net.bingyan.library.Book> _result;
    _data.writeInterfaceToken(DESCRIPTOR);
    try {
       mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
       _reply.readException();
       _result = _reply.createTypedArrayList(net.bingyan.library.Book.CREATOR);
       finally {
    }
      _reply.recycle();
      _data.recycle();
    }
    return _result;
}

They are automatically implemented by the compiler, and these two methods have many similarities; here is a hint: these two methods are the windows where the client process calls the server process. At the beginning of these two methods, they both define two Parcel (Chinese translation: package) objects. We are very familiar with the Parcel class; yes, the parameters of writeToParcel() in the Book class and createFromParcel() in CREATOR are of the Parcel type. The documentation explains this class as follows:

Container for a message (data and object references) that can be sent through an IBinder. A Parcel can contain both flattened data that will be unflattened on the other side of the IPC (using the various methods here for writing specific types, or the general {@link Parcelable} interface), and references to live {@link IBinder} objects that will result in the other side receiving a proxy IBinder connected with the original IBinder in the Parcel.

Translation: Proxy is a container that can pass messages through IBinder. A Parcel can contain serializable data, which will be deserialized on the other end of IPC; it can also contain references to IBinder objects, which will result in the other end receiving a proxy IBinder connected to the original IBinder in the Parcel.

The following figure is used to illustrate this more intuitively:

Android process communication mechanism AIDL

As shown in the figure, we can see directly that the server communicates with the client through Binder, relying on Parcel as the data package. The data package is a serialized object.

As mentioned above, both methods define two Parcel objects, respectively called _data and _reply. To put it in a more intuitive way, from the perspective of the client, _data is the data package sent by the client to the server, and _reply is the data package sent by the server to the client.

After that, communication with the service-side begins using these two objects. We can observe that there is a method call mRemote.transact() in both methods, which has four parameters. The meaning of the first parameter will be explained later. The second parameter _data is responsible for sending data packages to the service-side, such as interface method parameters, and the third parameter _reply is responsible for receiving data packages from the service-side, such as interface method return values. This line of code is only a simple method call, but it is the core part of AIDL communication. It actually performs a remote method call (the client calls the service-side Stub method with the interface method exposed by the local Proxy), so it can be thought of as a time-consuming operation.

In our example:

1The method .void addBook(Book book) needs to send the parameter Book:book to the service-side with the help of _data. The way to send it is to package Book through its implemented writeToParcel(Parcel out) method into _data, as you can imagine, _data is actually the parameter out. Do you remember the implementation of this method in Book? We pack each field of Book into the Parcel.

2The method .List<Book> getBookList() needs to receive the returned value List<Book>:books from the service-side with the help of _reply. The method does this by passing the static field CREATOR of Book as a parameter to the createTypedArrayList() method of _reply. Do you remember the CREATOR in Book? Were you curious about how to use this static field at that time? Now everything is clear, we need to rely on this object (for ease of understanding, we can call it a 'deserializer') to deserialize the data from the service-side and regenerate serializable objects or object arrays. It is clear that CREATOR uses _reply to generate List<Book>:books.

Of course, the _data and _reply in these two methods not only pass objects but also pass some validation information. This is something we don't need to delve into, but it should be noted that the packaging order and unpacking order of Parcel must be strictly consistent. For example, if the first packaged is int:i, then the first unpacked should also be this integer value. That is, if the first call during packaging is Parcel.writeInt(int), then the first call during unpacking should be Parcel.readInt().

Here, the Proxy explanation of the client is completed, and next we will take a look at the service-side Stub.

One of the methods implemented by Stub of IBookManager is very simple, which is simply returning itself because Stub itself inherits from Binder, and Binder inherits from IBinder, so there is no problem. You may ask: there are still two methods not implemented? These two methods are the interface methods we defined, which are left for the server process to implement, that is, we need to define an implementer of Stub in the server process at that time. The following analyzes the two important methods in Stub:

IBookManager asInterface(IBinder obj)

public static net.bingyan.library.IBookManager asInterface(android.os.IBinder obj) {
      if ((obj == null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin != null) && (iin instanceof net.bingyan.library.IBookManager))) {
        return ((net.bingyan.library.IBookManager) iin);
      }
      return new net.bingyan.library.IBookManager.Stub.Proxy(obj);
    }

The function of this method is to convert the Stub class to the IBookManager interface. The method contains a judgment: if our server process and client process are in the same process, then the Stub class is directly converted to IBookManager through type casting; if they are not in the same process, then the Stub is converted to IBookManager through the Proxy class. Why do this? We know that if the server process and client process are not in the same process, their memory cannot be shared, and they cannot communicate through general means. But if we implement the inter-process communication method ourselves, it will be too costly for ordinary developers. Therefore, the compiler helps us generate a tool encapsulating inter-process communication, that is, this Proxy. This class encapsulates the underlying inter-process communication mechanism and only exposes the interface methods. The client only needs to call these two methods to realize inter-process communication (which is actually method remote invocation) without needing to understand the details.

With this method, we can use it on the client to convert an IBinder type variable into the interface IBookManager we define, and its usage scenarios will be explained in the subsequent examples.

onTransact(int code, Parcel data, Parcel reply, int flags)

@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: {
          reply.writeString(DESCRIPTOR);
          return true;
        }
        case TRANSACTION_addBook: {
          data.enforceInterface(DESCRIPTOR);
          net.bingyan.library.Book _arg0;
          if ((0 != data.readInt())) {
            _arg0 = net.bingyan.library.Book.CREATOR.createFromParcel(data);
          } else {
            _arg0 = null;
          }
          this.addBook(_arg0);
          reply.writeNoException();
          return true; /* http://www.manongjc.com/article/1499.html */
        }
        case TRANSACTION_getBookList: {
          data.enforceInterface(DESCRIPTOR);
          java.util.List<net.bingyan.library.Book> _result = this.getBookList();
          reply.writeNoException();
          reply.writeTypedList(_result);
          return true;
        }
      }
      return super.onTransact(code, data, reply, flags);
}

Are we also very familiar with this method? We also see a similar method transact(int, Parcel, Parcel, int) in Proxy, with the same parameters, and they are both methods in Binder. Then, what is their relationship?

As mentioned earlier, transact() performs a remote call. If transact() is the initiator of the remote call, then onTransact() is the response to the remote call. The real process is that the client initiates a remote method call, the Android system responds to and processes this call through underlying code, then calls the onTransact() method of the server-side, extracts the method parameters from the data packet, hands them over to the same method call implemented by the server-side, and finally packages the return value and returns it to the client.

It should be noted that onTransact() is performed in the Binder thread pool of the server-side process, which means that if we need to update the UI in the onTransact() method, we must rely on Handler.

The meaning of the first parameter of these two methods is the identifier code of the AIDL interface method. In the Stub, two constants are defined as the identifiers for these two methods:

static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
   static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1;

If code == TRANSACTION_addBook, it means that the client calls addBook(); if code == TRANSACTION_getBookList, then the client calls getBookList(), and then it is handled by the corresponding server-side method. A diagram can be used to illustrate the entire communication process:

Android process communication mechanism AIDL

After understanding the entire process of AIDL, the next step is the application of AIDL in Android programs.

 The use of AIDL

Believe it or not, everyone should be familiar with the use of Service. Although Service is called 'service' and runs in the background, by default, it still runs in the main thread of the default process. In fact, making Service run in the default process is a bit of a misuse of talent. Many system services in Android run in separate processes, providing calls for other applications, such as window management services. The advantage of this is that multiple applications can share the same service, save resources, and facilitate centralized management of various clients, and the problem to be aware of is thread safety issues.

So next, we will implement a simple CS architecture book management system using AIDL.

Firstly, we define the server side:

BookManagerService

public class BookManagerService extends Service {
  private final List<Book> mLibrary = new ArrayList<>();
  private IBookManager mBookManager = new IBookManager.Stub() {
    @Override
    public void addBook(Book book) throws RemoteException {
      synchronized (mLibrary) {
        mLibrary.add(book);
        Log.d("BookManagerService", "now our library has ") + mLibrary.size() + " books");
      }
    }
    @Override
    public List<Book> getBookList() throws RemoteException {
      return mLibrary;
    }
  };
  @Override /* http://www.manongjc.com/article/1496.html */
  public IBinder onBind(Intent intent) {
    return mBookManager.asBinder();
  }
}
<service
   android:process=":remote"
   android:name=".BookManagerService"/>

On the server side, we define the BookManagerService class, within which we create the server-side Stub object and implement the two required AIDL interface methods to define the server's book management strategy. In the onBind() method, we return the IBookManager object as an IBinder. We know that when we bind a service, the system calls the onBinder() method to obtain the server's IBinder object and converts it into a client's IBinder object to pass it to the client. Although the server's IBinder and the client's IBinder are two IBinder objects, they are both the same object at the bottom level. When we register the Service in xml, we specify the process name so that the Service can run in a separate process.

Let's take a look at the implementation of the client:

Client

public class Client extends AppCompatActivity {
  private TextView textView;
  private IBookManager bookManager;
  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.library_book_manager_system_client);
    Intent i = new Intent(Client.this, BookManagerService.class);
    bindService(i, conn, BIND_AUTO_CREATE);
    Button addABook = (Button) findViewById(R.id.button);
    addABook.setOnClickListener(v -> {
      if (bookManager == null) return;
      try {
        bookManager.addBook(new Book(0, "book"));
        textView.setText(getString(R.string.book_management_system_book_count, String.valueOf(bookManager.getBookList().size())));
      } catch (RemoteException e) {
        e.printStackTrace();
      }
    });
    textView = (TextView) findViewById(R.id.textView);
  }
  private ServiceConnection conn = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
      Log.d("Client" --">", service.toString());
      bookManager = IBookManager.Stub.asInterface(service);
    }
    @Override
    public void onServiceDisconnected(ComponentName name) {
      Log.d("Client", name.toString());
    }
  };
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical" android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:weightSum="1"
  android:gravity="center">
  <Button
    android:text="http://www.manongjc.com/article/1495.html"
    android:layout_width="111dp"
    android:layout_height="wrap_content"
    android:id="@"+id/button" />
  <TextView
    android:layout_marginTop="10dp"
    android:text="@string/book_management_system_book_count"
    android:layout_width="231dp"
    android:gravity="center"
    android:layout_height="wrap_content"
    android:id="@"+id/textView" />
</LinearLayout>

Our client is an Activity, and the binding of the service is performed in onCreate(). The bindService() method has a parameter ServiceConnection:conn. Since the binding of the service is asynchronous, the role of this parameter is to call back the interface after the service is bound successfully. It has two callback methods: one is called after the service is connected, and the other is called after the connection with the server is disconnected. What we are concerned about now is mainly the onServiceConnected() method, where we only do one thing: convert the IBinder object passed from the server to the AIDL interface. We define the IBookManager:bookManager field to keep a reference to it. In this way, we can make remote method calls through this bookManager. We register an event for the client's Button: every time it is clicked, it will add a book to the server and display the number of books in the library currently.

Now let's take a look at the running effect of the program:

Every time we click the button, we successfully add a book to the server, indicating that we have successfully communicated across processes through AIDL.

Thank you for reading, and I hope it can help everyone. Thank you for your support of this site!

Statement: The content of this article is from the Internet, and the copyright belongs to the original author. The content is contributed and uploaded by Internet users spontaneously. This website does not own the copyright, has not been manually edited, and does not assume any relevant legal liability. If you find any content suspected of copyright infringement, please send an email to notice#w3Please send an email to codebox.com (replace # with @ when sending an email) to report any violations, and provide relevant evidence. Once verified, this site will immediately delete the content suspected of infringement.

You May Also Like