AIDL:Android Interface Definition Language.

What is the AIDL

Android processes cannot share memory. Therefore, you need to provide some mechanism for data communication between different processes.

In order to enable other applications to access the services provided by this application, the Android system uses Remote Procedure Call (RPC) to achieve. Like many other RPC-based solutions, Android uses an Interface Definition Language (IDL) to expose the interfaces of services. We know that three of Android’s four major components (Activities, BroadcastReceiver, and ContentProvider) can be accessed across processes, as can another Android component, Service. This cross-process accessible service can therefore be called an AIDL (Android Interface Definition Language) service.

Before introducing AIDL and other features, Binder is the core of AIDL.

Binder mechanism for Android

I’ve read some articles about binders. In general, the underlying implementation of Binder mechanisms is complex, quite complex, and takes a lot of time to fully understand. With Binders, I think you just need to understand the underlying principles and how to use them.

Binder is a class in the Android source code that implements the IBinder interface. Binder is a cross-process communication method in Android from an IPC perspective. From an Android Framework perspective, Binder is the bridge between ServiceManager managers (ActivityManager, WindowManager, etc.) and corresponding ManagerServices. From the Perspective of Android application layer, Binder is the communication medium between client and server. When bindService is implemented, the server will return a Binder object containing the service invocation of the server. Through this Binder object, the client can communicate with the server. Services here include normal services and AIDL-based services.

Next, let’s look at how Binder works with an AIDL example. Create a package named aidl in the project directory, and then create book. Java, book. aidl. Create book.aidl) and iBookManager.aidl as follows:

// Book.java package com.cy.ipcsample.aidl; import android.os.Parcel; import android.os.Parcelable; /** * public class Book implements Parcelable {@author cspecialy * @version v1.0.0 * @date 2018/5/14 21:38 */ 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(Parcelin) {
        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) {
            returnnew Book[size]; }}; @Override public intdescribeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeInt(bookId);
        parcel.writeString(bookName);
    }

    @Override
    public String toString() {
        return "Book{" +
                "bookId=" + bookId +
                ", bookName='" + bookName + '\''+'}'; }}Copy the code

 

// Book.aidl
package com.cy.ipcsample.aidl;

parcelable Book;
Copy the code

 

// IBookManager.aidl
package com.cy.ipcsample.aidl;

import com.cy.ipcsample.aidl.Book;
import com.cy.ipcsample.aidl.IOnNewBookArrivedListener;

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
    void registerListener(IOnNewBookArrivedListener listener);
    void unRegisterListener(IOnNewBookArrivedListener listener);
}
Copy the code

Once you’ve created the three files, compile them and the AS will be inapp/build/generated/source/aidl/debugA class named iBookManager.java is generated in the com.cy.ipcsample package in the directory, as shown below:

This is the system generated Binder class, and we will use this class to analyze how Binder works. The code is as follows :(the generated code format is messy, you can format the code to see after)

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: G:\\Android\\Github\\Bugly-Android-Demo\\sample\\ipcsample\\src\\main\\aidl\\com\\cy\\ipcsample
 * \\aidl\\IBookManager.aidl
 */
package com.cy.ipcsample.aidl;

public interface IBookManager extends android.os.IInterface {
    public java.util.List<com.cy.ipcsample.aidl.Book> getBookList() throws android.os.RemoteException;

    public void addBook(com.cy.ipcsample.aidl.Book book) throws android.os.RemoteException;

    /** Local-side IPC implementation stub class. */
    public static abstract class Stub extends android.os.Binder implements com.cy.ipcsample.aidl.IBookManager {
        static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        private static final java.lang.String DESCRIPTOR = "com.cy.ipcsample.aidl.IBookManager";

        /** Construct the stub at attach it to the interface. */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.cy.ipcsample.aidl.IBookManager interface,
         * generating a proxy if needed.
         */
        public static com.cy.ipcsample.aidl.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if(((iin ! = null) && (iin instanceof com.cy.ipcsample.aidl.IBookManager))) {return ((com.cy.ipcsample.aidl.IBookManager) iin);
            }
            return new com.cy.ipcsample.aidl.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_getBookList: {
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List<com.cy.ipcsample.aidl.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    com.cy.ipcsample.aidl.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.cy.ipcsample.aidl.Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true; }}return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.cy.ipcsample.aidl.IBookManager {
            private android.os.IBinder mRemote;

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

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            @Override
            public java.util.List<com.cy.ipcsample.aidl.Book> getBookList() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<com.cy.ipcsample.aidl.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.cy.ipcsample.aidl.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void addBook(com.cy.ipcsample.aidl.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);
                    }
                    mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }


        }
    }
}
Copy the code

The system generates an IBookManager interface that inherits from IInterface, so all interfaces that can be transmitted with Binder must inherit from IInterface.

Now let’s look at how this class works. If you look closely, you can see that the class is divided into three main parts:

  • Define your own methods (getBookList and addBook);
  • Internal static class -Stub, which inherits Binder and implements the IBookManager interface
  • Stub’s internal Proxy class -Proxy also implements the IBookManager interface

Let’s ignore the first part and focus on the Stub and Proxy classes. In Stub, you first declare two integer variables that identify the IBookManager method and which method is requested by the client during transact. Next, there is the asInterface method, which converts the Binder objects on the server side into objects of the type of AIDL interface required by the client by calling Binder’s queryLocalInterface method, Check whether the client and server are in the same process. If the client and server are in the same process, this method directly returns the Stub object on the server. Otherwise, the Stub object Proxy is returned. QueryLocalInterface is implemented as follows:

/**
 * Use information supplied to attachInterface() to return the
 * associated IInterface if it matches the requested
 * descriptor.
 */
public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
    if (mDescriptor.equals(descriptor)) {
        return mOwner;
    }
    return null;
}
Copy the code

MOwner is the this parameter passed in the Stub constructor.

Stub.Proxy is an object that runs on the client (returned to the client by the asInterface method). After being created, stub. Proxy has Binder objects on the server. Used to invoke server-side methods for remote invocation when requested by the client. When the client sends a request to the server, it invokes the corresponding stub. Proxy method. The process of the stub. Proxy method is as follows:

  • First create the input Parcel object _data, output Parcel object _reply, and return value object (if any) needed for the method;
  • Then write the method’s parameter information to _data (if any);
  • The Transact method is then called to make an RPC (remote procedure call) request while the current thread is suspended;
  • The server’s onTransact method is then called by the Transact method through the Binder object on the server side, that is, the onTransact method in the Stub.
  • After the onTransact method returns, the current thread continues execution and retrieves the result returned by the RPC procedure from _reply;
  • Finally, the data in _reply is returned (if the client request method requires a value).

This is how the system-generated IBookManager works. Note that the onTransact method on the server runs in a Binder thread pool. The iBookManager. Stub class inherits Binder, so Binder works.

  • When the client and server are connected (usually through the bindService method), the server returns an object of type IInterface to the client through the asInterface method (Binder object on the server if the client and server are in the same process). Otherwise return proxies for server-side Binder objects);
  • The client makes requests to the server through this object. If the client and the server are not in the same process, RPC requests are made through the Binder objects that the client and the server represent. Otherwise, the corresponding methods of the server are directly called through the Binder objects.

Or refer to the following figure to understand:

Binder plays an important role in AIDL and is at the heart of AIDL. Understanding how Binder works is useful in many ways.

The use of an AIDL

The steps to build an AIDL service are as follows:

  • The.aidl file is created and the system generates the corresponding interface class that inherits IInterface (the interface exposed to the client).
  • Create a Service that implements the interface in the.aidl file.
  • Create a client and bind the Service to the server.
  • After the client binds the server successfully, the Binder object returned by the server is converted to the IInterface type of the AIDL interface. Call methods in AIDL to communicate with the server.

Next, we use the IBookManager above to implement AIDL.

  1. Aidl and iBookManager. aidl files can be created directly using book. aidl and iBookManager. aidl files
  2. Create a Service named BookManagerService with the following code:

    package com.cy.ipcsample.aidl
    
    import android.app.Service
    import android.content.Intent
    import android.os.IBinder
    import android.os.RemoteCallbackList
    import android.util.Log
    import java.util.concurrent.CopyOnWriteArrayList
    
    class BookManagerService : Service() {
        private val TAG = "BookManagerService"private val mBookList = CopyOnWriteArrayList<Book>() private val mListenerList = RemoteCallbackList < IOnNewBookArrivedListener > * * * ()/Binder object implementing the AIDL interface, the client binding service side, Return this Binder object */ private val mBinder = object: IBookManager.Stub() {
    
            override fun getBookList(): MutableList<Book> {
                return mBookList
            }
    
            override fun addBook(book: Book?) {
                mBookList.add(book)
            }
    
        }
    
        override fun onBind(intent: Intent): IBinder {
            return mBinder
        }
    
        override fun onCreate() {super.oncreate () // Create two books mbooklist.add (Book(1,"Android"))
            mBookList.add(Book(2, "iOS"))}}Copy the code

    Then register the Service in AndroidManifest, and note the start of multiple processes:

    <service
        android:name=".aidl.BookManagerService"
        android:process="com.cy.ipcsample.bookManagerService">
    </service>
    Copy the code
  3. The code for binding the remote Service is as follows:

    private val mConnection = object : ServiceConnection { override fun onServiceDisconnected(name: ComponentName?) {} /** * Override fun onServiceConnected(name: ComponentName? , service: IBinder?) {}} // Bind the remote servicebindService(Intent(this, BookManagerService::class.java),
            mConnection, Context.BIND_AUTO_CREATE)
    Copy the code
  4. After binding to the server, first convert the Binder object returned by the server into the object of the AIDL interface in the ServiceConnection callback, and then call the corresponding method to communicate with the server. The code is as follows:

    / / the server returns the Binder object into IBookManager val bookManager. = IBookManager Stub. AsInterface (service) / / communicate with the server try {/ / Val list = bookManager.booklist log. I (TAG,"query book list, list type: ${list.javaClass.canonicalName}")
        Log.i(TAG, "query book list: $list"Val book = book (3,"The Art of Android Development")
        bookManager.addBook(book)
        Log.i(TAG, "add book: $book"Val newList = bookManager.booklist log. I (TAG,"query book list: $newList")
    } catch (e: RemoteException) {
        e.printStackTrace()
    }
    Copy the code

    In the code, we first query the list of books on the server, then add a book to the server to explore Android Art development, and then query again to see if the addition is successful. Run log, as shown below:

    The results are consistent with the expected results. The complete client code is as follows:

    package com.cy.ipcsample.aidl
    
    import android.content.ComponentName
    import android.content.Context
    import android.content.Intent
    import android.content.ServiceConnection
    import android.os.Bundle
    import android.os.IBinder
    import android.os.RemoteException
    import android.support.v7.app.AppCompatActivity
    import android.util.Log
    import com.cy.ipcsample.R
    
    class BookManagerActivity : AppCompatActivity() {
    
        private val TAG = "BookManagerActivity"
    
        private val mConnection = object : ServiceConnection {
    
            override fun onServiceDisconnected(name: ComponentName?) {
                Log.d(TAG, "binder died."} /** * Override fun onServiceConnected(name: ComponentName? , service: IBinder?) {/ / will return to the service side Binder object into IBookManager object val bookManager. = IBookManager Stub. AsInterface (service) / / communicate with the server try {/ / Val list = bookManager.booklist log. I (TAG,"query book list, list type: ${list.javaClass.canonicalName}")
                    Log.i(TAG, "query book list: $list"Val book = book (3,"The Art of Android Development")
                    bookManager.addBook(book)
                    Log.i(TAG, "add book: $book"Val newList = bookManager.booklist log. I (TAG,"query book list: $newList")
                } catch (e: RemoteException) {
                    e.printStackTrace()
                }
            }
    
        }
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(r.layout.activity_book_manager) // Bind the remote servicebindService(Intent(this, BookManagerService::class.java),
                    mConnection, Context.BIND_AUTO_CREATE)
        }
    
        override fun onDestroy() {
            unbindService(mConnection)
            super.onDestroy()
        }
    }
    Copy the code

At this point, a simple EXAMPLE of AIDL is completed, of course, the use of AIDL is far from that simple, there are many scenarios to consider, such as: the client needs to be at the same time as the server state changes, similar to observe this pattern, then how to subscribe and unsubscribe; Binder’s Accidental Death, how to reconnect, etc

Asynchronous invocation of AIDL

Are AIDL calls synchronous or asynchronous?

The problem is that, as you know from the title of this section, AIDL calls are synchronous. At the same time, according to the above analysis of Binder mechanism, when the client makes a remote RPC request, the thread will hang and wait for the result. Therefore, it can be seen that AIDL call process is synchronous, which is verified below.

First, the getBookList method of the Binder object implemented in BookManagerService on the server side adds delayed execution, as shown below:

Then, add a button to the BookManagerActivity on the client side that calls the server Binder’s getBookList to make an RPC request when clicked. The code is as follows:

After running, click the button continuously, and the result is as shown below:

As can be seen from the figure, no response error (ANR) occurs for a period of time after the button is clicked continuously. It can be seen from the figure that when the client makes RPC request to the server, it is synchronous, that is:
AIDL calls are synchronous.

AIDL call process is synchronous, when we need the server to do time-consuming operations, it must not use synchronous call, otherwise the user experience will be affected, or directly ANR or application crash. So how do you make AIDL calls asynchronous?

It’s as simple as putting the call on a non-UI thread, and then using a Handler to do UI updates on the return of the call. As shown below:

In this paper, the reference

  • Chapter 2 in Exploring the Art of Android Development