AIDL stands for Android Interface Definition Language. By writing a simple AIDL file, Java template interface file can be generated automatically after compilation to carry out interprocess communication.

Create the AIDL file

You first need to create an AIDL folder in the same directory as Java, and then create packages and AIDL files.

By default, a new AIDL file creates a method that tells us which base types are supported

interface IMyAidlInterface {
    /**
     * 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);
}
Copy the code

In addition to int, long, Boolean, float, double, String, etc., you can also use complex data types that inherit Parcelable, and their lists, as described below.

Customize an interface for AIDL

Using the base type, create a method getName

interface IMyAidlInterface {
   
   String getName(int age);
   
   void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
           double aDouble, String aString);
}
Copy the code

Since AIDL is used for interprocess communication, you need to copy the AIDL file to another process (you can create a new project). You need to ensure that the package names of aiDL files in both processes are the same, otherwise an error will be reported.

After you build the project, you will find that the corresponding Java interface is automatically generated in the Build directory

Simple use of AIDL

  • The server process creates a Service and returns a Binder
public class MyService extends Service { @Override public IBinder onBind(Intent intent) { return new MyBinder(); } class MyBinder extends IMyAidlInterface.Stub { @Override public String getName(int age) throws RemoteException { return age > 30 ? "Lao Wang" : "Xiao Wang "; } @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { } } }Copy the code

Register the Service in androidManifest.xml

<service android:name=".MyService">
    <intent-filter>
        <actaion android:name="com.xfz.aidlapplication.MyService123" />
    </intent-filter>
</service>
Copy the code

You can set Android :process to make the service a separate process. You can set Android :permission to make the service accessible only to the processes that have the permission

  • The client creates a ServiceConnection and binds it to the service

First, create the intent:

You can use Component, passing in the package name and the service class name

Intent intent = new Intent();
intent.setComponent(new ComponentName("com.xfz.aidlapplication", "com.xfz.aidlapplication.MyService"));
Copy the code

You can also use actions and packages

Intent intent = new Intent();
intent.setAction("com.xfz.aidlapplication.MyService123");
intent.setPackage("com.xfz.aidlapplication");
Copy the code

Binding and calling

public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Intent intent = new Intent(); intent.setComponent(new ComponentName("com.xfz.aidlapplication", "com.xfz.aidlapplication.MyService")); bindService(intent, mConnection, Service.BIND_AUTO_CREATE); } private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) {/ / in the service connections after a successful call IMyAidlInterface mIntentionPlus = IMyAidlInterface. The Stub. AsInterface (service); try { Log.d("xfz", "name = " + mIntentionPlus.getName(35)); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } }; }Copy the code

Create a complex data type that inherits Parcelable

Data objects that inherit from Parcelable can be generated using plug-ins, starting with defining a simple data model

public class People {
    String mName;
    int mAge;
}
Copy the code

Install android- Parcelable – Intellij -plugin, you can automatically generate parcelable objects in one step.

Note: There is a problem with the latest Android Studio to install the plugin, and a supported plugin was later found in the Issue

When the plug-in is installed, right-click on the class and select Generate → parcelable

Finally, a parcelable object is generated

public class People implements Parcelable { public String mName; public int mAge; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(this.mName); dest.writeInt(this.mAge); } public People() { } protected People(Parcel in) { this.mName = in.readString(); this.mAge = in.readInt(); } public static final Creator<People> CREATOR = new Creator<People>() { @Override public People createFromParcel(Parcel source) { return new People(source); } @Override public People[] newArray(int size) { return new People[size]; }}; }Copy the code

Use complex data types that inherit from Parcelable

  1. Create a people. aidl file in the same path as the People Java folder.
package com.xfz.aidlapplication.model;

parcelable People;
Copy the code

This makes it possible to reference the People type in AIDL.

  1. Modify the AIDL file and write a method that uses the type People. Note that overloading is not supported in AIDL.

The new method is getAge, as follows:

  • First, you need to manually import People
  • When using People, add the in tag before (the in tag is described below)
package com.xfz.aidlapplication;

import com.xfz.aidlapplication.model.People;

interface IMyAidlInterface {

    String getName(int age);

    int getAge(in People people);

    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}
Copy the code

Modify MyBinder

class MyBinder extends IMyAidlInterface.Stub { @Override public String getName(int age) throws RemoteException { return age > 30 ? "Lao Wang" : "Xiao Wang "; } @Override public int getAge(People people) throws RemoteException { return people.mAge; } @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { } }Copy the code

People. Java and the corresponding AIDL are also synchronized to the client process so that they can be used in the client process

private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName  name, IBinder service) {/ / in the service connections after a successful call IMyAidlInterface IMyAidlInterface = IMyAidlInterface. The Stub. AsInterface (service); try { People people = new People(); people.mAge = 35; People. MName = "xiao Wang "; Log.e("xfz", "age = " + iMyAidlInterface.getAge(people) + " name = " + people.mName); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } };Copy the code

AIDL Directional tag: in, Out, or INout

The directional tag for arguments of basic types (int, long, Boolean, float, double, String) is in, and only in. Complex data type parameters require a directional tag to determine where the data is going.

Data Flow:

  • In indicates that data can flow from the client to the server only
  • Out indicates that data can only flow from the server to the client
  • Inout means that data flows in both directions between the server and the client

More specifically, the data flow is specific to the object in the client that passes in the method.

  • If in is a directed tag, the server will receive the full data of that object, but the client object will not be changed by the server’s modification of the parameter.
  • If out is a directed tag, the server will receive an empty object for that object, but the client will change it synchronously if the server makes any changes to the received empty object.
  • In the case that inout is a directed tag, the server will receive complete information about the object from the client, and the client will synchronize any changes made to the object by the server.

Modifying MyBinder above and later verifying, getAge assigns name:

class MyBinder extends IMyAidlInterface.Stub { @Override public String getName(int age) throws RemoteException { return age > 30 ? "Lao Wang" : "Xiao Wang "; } @Override public int getAge(People people) throws RemoteException { people.mName = getName(people.mAge); return people.mAge; } @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { } }Copy the code

If People is still in, then the output is still xiao Wang, because the server’s changes to the passed parameter do not affect the client, so you need to change People to inout, so the output will be Lao Wang.

Interface IMyAidlInterface {... int getAge(inout People people); ... }Copy the code

Note that after changing to out/inout, you will find that the Java file generated by AIDL does not find the readFromParcel method, so you need to add it yourself, consistent with the function body and constructor

Public class People implements Parcelable {...... protected People(Parcel in) { this.mName = in.readString(); this.mAge = in.readInt(); } public void readFromParcel(Parcel in) { this.mName = in.readString(); this.mAge = in.readInt(); }... }Copy the code

Differences between in/Out/Inout Generated Java files

When in is used, the writeToParcel method is called to transfer data from the client to the server

@Override public int getAge(com.vivo.aiengine.abilityhub.People people, com.vivo.aiengine.abilityhub.ICallbackInterface1 callback) throws android.os.RemoteException { android.os.Parcel _data =  android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); int _result; try { _data.writeInterfaceToken(DESCRIPTOR); //------------------in------------------ if ((people ! = null)) { _data.writeInt(1); people.writeToParcel(_data, 0); } else { _data.writeInt(0); } //------------------in------------------ _data.writeStrongBinder((((callback ! = null)) ? (callback.asBinder()) : (null))); boolean _status = mRemote.transact(Stub.TRANSACTION_getAge, _data, _reply, 0); if (! _status && getDefaultImpl() ! = null) { return getDefaultImpl().getAge(people, callback); } _reply.readException(); _result = _reply.readInt(); } finally { _reply.recycle(); _data.recycle(); } return _result; }Copy the code

Out, the readFromParcel method is called to transfer data from the server to the client

@Override public int getAge(com.vivo.aiengine.abilityhub.People people, com.vivo.aiengine.abilityhub.ICallbackInterface1 callback) throws android.os.RemoteException { android.os.Parcel _data =  android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); int _result; try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeStrongBinder((((callback ! = null)) ? (callback.asBinder()) : (null))); boolean _status = mRemote.transact(Stub.TRANSACTION_getAge, _data, _reply, 0); if (! _status && getDefaultImpl() ! = null) { return getDefaultImpl().getAge(people, callback); } _reply.readException(); _result = _reply.readInt(); //------------------out------------------ if ((0 ! = _reply.readInt())) { people.readFromParcel(_reply); } //------------------out------------------ } finally { _reply.recycle(); _data.recycle(); } return _result; }Copy the code

// Inout has both writeToParcel and readFromParcel methods

The oneway keyword in AIDL

Oneway has two main features: asynchronous invocation and serialization.

  • An asynchronous call is an application that sends data to the Binder driver without suspending the thread to wait for a response from the binder driver. For example, some system services will use Oneway when they call the application process. For example, AMS calls the application process to start the Activity. In this way, even if the application process does time-consuming tasks, the system service will not be blocked.
  • Serialization means that for a server-side AIDL interface, all oneway methods are not executed at the same time. The binder driver serializes them and queues them one at a time.

Oneway can be used to decorate an interface so that all methods within an interface implicitly refer to oneway. Oneway can modify methods in an interface. Methods decorated by oneway may not have a return value and may not take out or inout arguments.

For more details, please refer to this article if you really understand AIDL’s Oneway

Use OneWay for asynchronous callbacks

First, add an interface for callback, icallBackInterface.aidl

interface ICallbackInterface {
    oneway void callback(int totalName);
}
Copy the code

Add a new oneWay method getTotalAgeAsync to IMyAidlInterface.

In conjunction with the List mentioned above, you need to manually import the ICallbackInterface and List classpath

package com.xfz.aidlapplication;

import com.xfz.aidlapplication.model.People;
import com.xfz.aidlapplication.ICallbackInterface;

import java.util.List;

interface IMyAidlInterface {

    String getName(int age);

    int getAge(inout People people);

    oneway void getTotalAgeAsync(in List<People> peopleList, ICallbackInterface callback);

    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}
Copy the code

Then it’s implemented in MyBinder

Class MyBinder extends IMyAidlInterface.Stub {...... @Override public void getTotalAgeAsync(List<People> peopleList, ICallbackInterface callback) throws RemoteException { if (peopleList == null || peopleList.size() == 0 || callback == null) { return; Int totalAge = 0; int totalAge = 0; for (People people : peopleList) { totalAge += people.mAge; } callback.callback(totalAge); }... }Copy the code

After the modified AILD file is synchronized to the client process, invoke it

A Service returns multiple binders

The onBind method of a Service returns a Binder when bound. If the bind is not unbound, the onBind method will not be executed. So even if a different type is passed in the Intent the second time to acquire a new Binder, it will fail.

Solution: Service returns a factory binder that can be used to generate different binder classes.

Start by creating an ibinderFactory.aidl

interface IBinderFactory {
    IBinder generateBinder(int binderType);
}
Copy the code

The Binder implementation class returns the Binder based on the binderType.

public class MyService extends Service { public static final int TYPE_MY_BINDER = 0; @Override public IBinder onBind(Intent intent) { return new BinderFactory(); } class BinderFactory extends IBinderFactory.Stub { @Override public IBinder generateBinder(int binderType) throws RemoteException { IBinder binder; switch (binderType) { case TYPE_MY_BINDER: default: binder = new MyBinder(); } return binder; }}... }Copy the code

The client obtains the BinderFactory and then the MyBinder

private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName Name, IBinder service) {/ / in the service connection after a successful call IBinderFactory IBinderFactory = IBinderFactory. The Stub. AsInterface (service); IMyAidlInterface iMyAidlInterface = null; try { iMyAidlInterface = IMyAidlInterface.Stub.asInterface(iBinderFactory.generateBinder(TYPE_MY_BINDER)); } catch (RemoteException e) { e.printStackTrace(); }... }Copy the code

The use of Messenger

Messenger is a lightweight inter-process communication, internal use of AIDL + handler to achieve, easy to use, the disadvantage is serial communication, not suitable for high concurrency scenarios; There is also a one-way channel (the client sends messages to the server) +

Let’s start using it directly:

Server: Pass a handler to create a Messenger, and get the Binder from Messenger

So when the client sends a message to the server, the message goes to the message queue held by the handler

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new Messenger(mHandler).getBinder();
    }
Copy the code

Client: In the onServiceConnected method, you use the returned IBinder to create a Messenger to send messages to the server

    private ServiceConnection mServiceConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Message msg = Message.obtain();
            msg.what=0;
            try {
                new Messenger(service).send(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            
        }
    };
Copy the code

The server is aware of client destruction

After a client connects to a server, if the server is disconnected, the onServiceDisconnected method of the client’s ServiceConnection is triggered to let the client know that the service is disconnected.

But how does the server know that the client is down? There are requirements for the server to record things when the client connects to the server and then remove them when the client disconnects, so the server also needs to know if the client crashes unexpectedly.

  1. Use linkToDeath to set up the death proxy

After the client process passes a binder to the server process and the server obtains the binder, linkToDeath can be called

Bridgecallback.asbinder ().linktoDeath (new DeathRecipient() {@override public void binderDied() {//do something... //unlink unlinkToDeath(this, 0); }}, 0);Copy the code

With the above Settings on the server, the binderDied() method is triggered when the client process dies.

  1. Use RemoteCallbackList, which is actually a wrapper around linkToDeath

When a server retrieves an AIDL interface from a client process and calls the RemoteCallbackList register method, it generates an internal Callback that inherits from DeathRecipient. Then asBinder is called to turn the interface into an IBinder, and linkToDeath is called.

When the client process dies, the onCallbackDied method is called, but to use this callback, you need to inherit RemoteCallbackList. Then write onCallbackDied