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
- 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.
- 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.
- 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.
- 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