This article reprints the article, read the original source code can be obtained, at the end of the article has the original link

PS: This article is about using AIDL for inter-process communication, the demo is written in Kotlin language

1. Use AIDL

The full name of AIDL is Android Interface Definition Language, which is also the Android Interface Definition Language. AIDL can also be used to realize cross-process method call. In the second part of the last article, We use Messenger for cross-process communication. AIDL is the underlying implementation of Messenger, so Messenger is essentially AIDL, and the system encapsulates it for us to make it easier to call. When Messenger communicates across processes, the request queue is synchronous and cannot be executed concurrently, which is not applicable in some cases where multiple processes are required — this is where AIDL is needed.

When using AIDL for cross-process communication, not all data is supported for transfer, and AIDL supports the following data:

1) Basic data types

2) String and CharSequence

3) List type: All elements in the List must be of one of the types supported by AIDL or an interface generated by another AIDL

4) Map: Only supports HashMap, and every element in it must be supported by AIDL, including Key and Value

5) Parcelable: All objects that implement the Parcelable interface

6) AIDL: All the AIDL interfaces themselves can be used in AIDL files

Here’s an example:

(1) in the project’s main folder to create an aidl file, the file named ICommunicationManager. Aidl

The picture

File ICommunicationManager. Aidl first create good code as shown below:

interface ICommunicationManager {

/**
 * Demonstrates some basic types that you can use as parameters
 * and return values in AIDL.
 */
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
        double aDouble, String aString);
        

}

Above ICommunicationManager aidl document code is generated automatically, we based on the add client and server communication methods, such as adding sendMessage method, note that the method written here is not in accordance with the kotlin grammar to write, It’s written in Java syntax; If other classes that we have customized are referenced in ICommunicationManager, we will manually write statements that import other classes, even if the other classes and ICommunicationManager are under the same package:

interface ICommunicationManager {

/**
 * Demonstrates some basic types that you can use as parameters
 * and return values in AIDL.
 */
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
        double aDouble, String aString);
        void sendMessage(in String msg);

}

After finish ICommunicationManager. Aidl code of this file to compile the project, after the compilation, In the app/build/generated \ source \ aidl \ debug \ com \ xe \ demo \ ipcdemo3 directory has a ICommunicationManager Java file, ICommunicationManager. Automatically generated in the Java code we don’t do analysis first, later analysis, the directory structure is as follows:

The picture

(2) Create a Kotlin class AIDLService and inherit it from Service:

class AIDLService: Service() {

var TAG: String = "AIDLService" var mBinder: Binder? = null override fun onBind(intent: Intent?) : IBinder { return mBinder!! } override fun onCreate() { super.onCreate() initBinder() } fun initBinder() { mBinder = ImplCommunicationManager() } inner class ImplCommunicationManager: ICommunicationManager.Stub() { override fun basicTypes(anInt: Int, aLong: Long, aBoolean: Boolean, aFloat: Float, aDouble: Double, aString: String?) { } override fun sendMessage(msg: String?) { Log.d(TAG,msg) } }

}

(3) Configure AidLService in AndroidManifest.xml file:

<service android:name=”com.xe.demo.ipcservice.AIDLService”

        android:process=":remote">

</service>

(4) Create a new Activity with the language of Kotlin and class name of AidLActivity:

class AIDLActivity : AppCompatActivity() {

var mServiceConnection: ServiceConnection? = null var mICommunicationManager: ICommunicationManager? = null var num: Int = 1 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_aidl) initServiceConnection() } fun initServiceConnection() { mServiceConnection = MyServiceConnection(); } fun onClick(v: View) { if (v.id == R.id.btn_1) { bindService() } else if (v.id == R.id.btn_2) { sendMessage() } } fun sendMessage() { Var MSG = "Send a message from the client" + num+ "to the server" Num+ + try {MicommunicationManager!! .sendMessage(msg) } catch (e: RemoteException) { } } fun bindService() { var intent: Intent = Intent(this, AIDLService::class.java) bindService(intent,mServiceConnection, Context.BIND_AUTO_CREATE) } inner class MyServiceConnection: ServiceConnection { override fun onServiceConnected(name: ComponentName? , service: IBinder?) { mICommunicationManager = ICommunicationManager.Stub.asInterface(service) } override fun onServiceDisconnected(name: ComponentName?) {}}

}

(5) Write the layout file activity_aidl corresponding to AIDLActivity:

xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.xe.demo.ipcdemo3.AIDLActivity"> <TextView Android :layout_width="match_parent" android:text=" This is a client of AIDL "android:layout_height="wrap_content" /> <Button android:id="@+id/btn_1" android:layout_width="match_parent" android:onClick="onClick" Android :layout_height="wrap_content" android:text=" Start a service "/> <Button android:id="@+id/btn_2" android:layout_width="match_parent" android:onClick="onClick" android:layout_height="wrap_content" Android :text=" Send message to service "/>

</LinearLayout>

The interface to start the program is as follows:

The picture

When we click on the “open a service” button, and then click the “send a message to a service” button, and then have the following log printing, pay attention to the places in the following lap. Switch to the com xe. Demo. Ipcdemo3: remote, because our service AIDLService specify the process name that’s the one

The picture

As can be seen from the log, our service AIDLService receives the message sent by the client AIDLActivity via AIDL.

2, AIDL source analysis

First of all, we open the compiled ICommunicationManager. Java file, it is automatically generated

/ *

  • This file is auto-generated. DO NOT MODIFY.
  • Original file: D:\androidDemo\IPCDemo3\app\src\main\aidl\com\xe\demo\ipcdemo3\ICommunicationManager.aidl

    */

package com.xe.demo.ipcdemo3;

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

public interface ICommunicationManager extends android.os.IInterface {

/** * Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.xe.demo.ipcdemo3.ICommunicationManager { private static final java.lang.String DESCRIPTOR = "com.xe.demo.ipcdemo3.ICommunicationManager"; /** * Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.xe.demo.ipcdemo3.ICommunicationManager interface, * generating a proxy if needed. */ public static com.xe.demo.ipcdemo3.ICommunicationManager asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin ! = null) && (iin instanceof com.xe.demo.ipcdemo3.ICommunicationManager))) { return ((com.xe.demo.ipcdemo3.ICommunicationManager) iin); } return new com.xe.demo.ipcdemo3.ICommunicationManager.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_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(); java.lang.String _arg5; _arg5 = data.readString(); this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5); reply.writeNoException(); return true; } case TRANSACTION_sendMessage: { data.enforceInterface(DESCRIPTOR); java.lang.String _arg0; _arg0 = data.readString(); this.sendMessage(_arg0); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); } private static class Proxy implements com.xe.demo.ipcdemo3.ICommunicationManager { 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. */ @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) 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.writeInt(anInt); _data.writeLong(aLong); _data.writeInt(((aBoolean) ? (1) : (0))); _data.writeFloat(aFloat); _data.writeDouble(aDouble); _data.writeString(aString); mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } @Override public void sendMessage(java.lang.String msg) 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(msg); mRemote.transact(Stub.TRANSACTION_sendMessage, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } } static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_sendMessage = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); } /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException; public void sendMessage(java.lang.String msg) throws android.os.RemoteException;

}

The ICCommunicationManager interface inherits the Android.os. iInterface interface, which can transfer data in Binder, and declares the sendMessage method. We ICommunicationManager. Aidl declared in the method; At the same time, it used the id identifies this way, in fact, this id is used to identify the client in the process of transact the requested exactly is which method, we just in ICommunicationManager. Aidl document declares a method only, otherwise methods declared number is more, The more IDs you have, the same number of IDs and the same number of methods; It has a class called Stub, which is a Binder class. When the client and server are not in the same process, it will use the Transact method. This logic is done by the internal Proxy class Proxy of Stub, which implements the IInterface interface. Indicates that it has the capabilities that Server promises to clients.

2, 1 Stub AsInterface method

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

Its purpose is to determine if the client and server are in the same process, and if they are in the same process, it returns the local object IIN, Otherwise returns a Proxy object new com. Xe. Demo. Ipcdemo3. ICommunicationManager. Stub. The Proxy (obj), this means that when not in the same process, the client get is not the service side of Binder itself, but a copy.

2, 2 Stub Asbinder method

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

Returns the current Binder object, for example our demo class, which is the object of our new inner class, ImplCommunicationManager.

2, 3 Proxy SendMessage method

@Override public void sendMessage(java.lang.String msg) 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(msg); mRemote.transact(Stub.TRANSACTION_sendMessage, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); }}

The sendMessage method is we ICommunicationManager. Aidl document declared in, through the Proxy class Proxy to implement, in the sendMessage method by Parcel data serialization, The _data object is used to load the data and then transact the data to Binder’s server using the IBinder MRemote object while the current thread is suspended. The server’s onTransact method is then called until the remote procedure call returns, and the current thread continues to execute, fetching the result of the remote procedure call from _reply, and finally returning the data in _reply.

2, 4 DESCRIPTOR

private static final java.lang.String DESCRIPTOR = “com.xe.demo.ipcdemo3.ICommunicationManager”;

Unique identification of Binder, with current Binder said the name of the class, such as the current demo of the Binder class name com. Xe. Demo. Ipcdemo3. ICommunicationManager.

2, 5 Stub OnTransact method

@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_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(); java.lang.String _arg5; _arg5 = data.readString(); this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5); reply.writeNoException(); return true; } case TRANSACTION_sendMessage: { data.enforceInterface(DESCRIPTOR); java.lang.String _arg0; _arg0 = data.readString(); this.sendMessage(_arg0); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); }

This method runs in the server’s Binder thread pool. When a client makes a cross-process method call, the underlying system will wrap the remote method and hand it over to this method. The server identifies the remote method requested by the client through the code (for the remote method in this demo, the MicommunicationManager of the client AIDLActivity!! .sendMessage(MSG) request), if the remote method has parameters, it will fetch the parameters needed by the remote method from the data before executing the remote method; If the remote method has a return value, the return value is written to the reply. If the onTransact method returns false, the client’s request will fail.