Series of articles
Android interprocess communication (I) - Android multiprocess mode
Android interprocess communication (2) - understanding Binder's mechanism
Android interprocess communication (3) - understand Binder through AIDL and write Binder service
In the previous chapter, I have learned the communication principle of Binder. Here, I pass AIDL again. I will try again and write a Binder myself.
If you are not familiar with AIDL, you can refer to this article AIDL usage details and process callback
1, Basic use of AIDL
The above code is also used here. First, the task class TaskInfo needs to inherit the Parcelable interface and let as help you implement the method, as follows:
public class TaskInfo implements Parcelable { public int id; public String url; public int progress; public TaskInfo() { } protected TaskInfo(Parcel in) { id = in.readInt(); url = in.readString(); progress = in.readInt(); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(id); dest.writeString(url); dest.writeInt(progress); } @Override public int describeContents() { return 0; } public static final Creator<TaskInfo> CREATOR = new Creator<TaskInfo>() { @Override public TaskInfo createFromParcel(Parcel in) { return new TaskInfo(in); } @Override public TaskInfo[] newArray(int size) { return new TaskInfo[size]; } }; @Override public String toString() { return "TaskInfo{" + "id=" + id + ", url='" + url + '\'' + ", progress=" + progress + '}'; } }
And then there was TaskInfo.aidl
//Note that the TaskInfo must be in the com.example.ipcdemo Next, I might as well remind you that I can't find it. parcelable TaskInfo;
IRemoteService.aidl :
//Remember to import TaskInfo.aidl 's bag import com.example.ipcdemo.TaskInfo; interface IRemoteService { //Sum of two numbers int add(int num1,int num2); //Add a task TaskInfo addTask(in TaskInfo info); }
Then build it, and you will find that it is generated under build IRemoteService.java
Next, take a look at the java of IRemoteService. The code is a little long. You can skip:
public interface IRemoteService extends android.os.IInterface { /** Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.example.ipcdemo.IRemoteService { private static final java.lang.String DESCRIPTOR = "com.example.ipcdemo.IRemoteService"; /** Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.example.ipcdemo.IRemoteService interface, * generating a proxy if needed. */ public static com.example.ipcdemo.IRemoteService asInterface(android.os.IBinder obj) { if ((obj==null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin!=null)&&(iin instanceof com.example.ipcdemo.IRemoteService))) { return ((com.example.ipcdemo.IRemoteService)iin); } return new com.example.ipcdemo.IRemoteService.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 { java.lang.String descriptor = DESCRIPTOR; switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(descriptor); return true; } case TRANSACTION_add: { data.enforceInterface(descriptor); int _arg0; _arg0 = data.readInt(); int _arg1; _arg1 = data.readInt(); int _result = this.add(_arg0, _arg1); reply.writeNoException(); reply.writeInt(_result); return true; } case TRANSACTION_addTask: { data.enforceInterface(descriptor); com.example.ipcdemo.TaskInfo _arg0; if ((0!=data.readInt())) { _arg0 = com.example.ipcdemo.TaskInfo.CREATOR.createFromParcel(data); } else { _arg0 = null; } com.example.ipcdemo.TaskInfo _result = this.addTask(_arg0); reply.writeNoException(); if ((_result!=null)) { reply.writeInt(1); _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); } else { reply.writeInt(0); } return true; } default: { return super.onTransact(code, data, reply, flags); } } } private static class Proxy implements com.example.ipcdemo.IRemoteService { 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; } //Sum of two numbers @Override public int add(int num1, int num2) 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.writeInt(num1); _data.writeInt(num2); boolean _status = mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0); if (!_status && getDefaultImpl() != null) { return getDefaultImpl().add(num1, num2); } _reply.readException(); _result = _reply.readInt(); } finally { _reply.recycle(); _data.recycle(); } return _result; } //Add a task @Override public com.example.ipcdemo.TaskInfo addTask(com.example.ipcdemo.TaskInfo info) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); com.example.ipcdemo.TaskInfo _result; try { _data.writeInterfaceToken(DESCRIPTOR); if ((info!=null)) { _data.writeInt(1); info.writeToParcel(_data, 0); } else { _data.writeInt(0); } boolean _status = mRemote.transact(Stub.TRANSACTION_addTask, _data, _reply, 0); if (!_status && getDefaultImpl() != null) { return getDefaultImpl().addTask(info); } _reply.readException(); if ((0!=_reply.readInt())) { _result = com.example.ipcdemo.TaskInfo.CREATOR.createFromParcel(_reply); } else { _result = null; } } finally { _reply.recycle(); _data.recycle(); } return _result; } public static com.example.ipcdemo.IRemoteService sDefaultImpl; } static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_addTask = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); public static boolean setDefaultImpl(com.example.ipcdemo.IRemoteService impl) { if (Stub.Proxy.sDefaultImpl == null && impl != null) { Stub.Proxy.sDefaultImpl = impl; return true; } return false; } public static com.example.ipcdemo.IRemoteService getDefaultImpl() { return Stub.Proxy.sDefaultImpl; } } /** Default implementation for IRemoteService. */ public static class Default implements com.example.ipcdemo.IRemoteService { //Sum of two numbers @Override public int add(int num1, int num2) throws android.os.RemoteException { return 0; } //Add a task @Override public com.example.ipcdemo.TaskInfo addTask(com.example.ipcdemo.TaskInfo info) throws android.os.RemoteException { return null; } @Override public android.os.IBinder asBinder() { return null; } } //Sum of two numbers public int add(int num1, int num2) throws android.os.RemoteException; //Add a task public com.example.ipcdemo.TaskInfo addTask(com.example.ipcdemo.TaskInfo info) throws android.os.RemoteException;
Before you explain it, learn some concepts:
- Iinterface: the interface that the Aidl file must inherit. It has only one method, IBinder asBinder(), to implement its class. It represents the ability to transfer Binder objects across processes, or Binder proxy objects. For example, in the above code, this is returned, indicating that the inner class Stub has the function of cross processes.
- IBinder: the object that implements this interface has the ability of cross process book transmission. When the cross process data flows through the driver, the driver will recognize the Binder type data, so as to automatically complete the conversion of Binder local objects and Binder proxy objects in different processes.
2, Analyze Binder process
First, let's look at how we build Binder services when we are in services:
//AIDL's services, IRemoteService.Stub mBinder = new IRemoteService.Stub() { @Override public int add(int num1, int num2) throws RemoteException { /** * Here is the specific implementation method, such as directly returning the sum of two numbers */ Log.d(TAG, "zsr add: Two numbers passed by the client were received: "+num1+" "+num2); return (num1 + num2); } @Override public TaskInfo addTask(TaskInfo info) throws RemoteException { Log.d("zsr", "Receive information from client: "+info); info.id = 0; info.progress = 50; // mHandler.sendEmptyMessage(1); // mTaskInfo = info; return info; } };
As you can see, the sub class Stub of IRemoteService is used. Let's start with this class.
DESCRIPTOR
See that in the Stub subclass, a tag string is implemented, which is represented by package name plus type:
private static final java.lang.String DESCRIPTOR = "com.example.ipcdemo.IRemoteService";
Then in its constructor, register the current Binder and string:
public Stub() { this.attachInterface(this, DESCRIPTOR); }
When you need to get the binder from ServerManager later, you need this string, for example:
queryLocalInterface(DESCRIPTOR)
asInterface
Convert the server-side Binder object into all AIDL objects on the client side; for example:
IRemoteService mBinder = IRemoteService.Stub.asInterface(service);
This transformation is divided into processes. If the same process, you can directly return to itself. If it is a cross process, you need to use its proxy class to transform. It can also be seen from the code:
public static com.example.ipcdemo.IRemoteService asInterface(android.os.IBinder obj) { if ((obj==null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin!=null)&&(iin instanceof com.example.ipcdemo.IRemoteService))) { return ((com.example.ipcdemo.IRemoteService)iin); } return new com.example.ipcdemo.IRemoteService.Stub.Proxy(obj); }
asBinder
As we know from the above introduction, the returned object has the ability of cross process. Here, this is returned to represent Stub.
onTransact
This method runs in the Binder thread pool on the server side, where data writing and result reading are all done.
When the client initiates a cross process request, the remote request will be encapsulated at the bottom and then handed over to the secondary method for processing. From the code, you can know which method is currently executing, for example, we defined the index of add() and addTask()
static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_addTask = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
In this way, we can get the corresponding method by code:
Continue to see onTransact(int code, Parcel data, Parcel reply, int flags). Its parameters include the Parcel parameters that can be sequenced and deserialized, data and reply;
We can take out parameters from data (if there are parameters in the method), pay attention to the order, and then write the structure to replay, so when your method has a return value, replay will return what you need.
Note that onTransact needs to return true, and false means the request failed.
IRemoteService#Proxy
From the above to the above, when calling across processes, asInterface returns the strength of proxy class, which also implements IRemoteService interface Android interprocess communication (2) - understanding Binder's mechanism We know that the agent mode of Binder is that when A accesses the object method of B, B will not give the object to A, but will give it a specific agent class of the same method, and then A will pass the parameters to B through this agent, and B will calculate and then pass the results to A, so as to realize the role of cross process.
And it does:
First pass_ data write in all the parameters, and then use mRemote.transact The parameter is passed to the service. It calls the onTransact method, writes the result to reply through the thread pool, and finally returns it to Binder to complete the cross process action.
In this way, we have completed the analysis of AIDL
3, Custom AIDL service
From the above point of view, in AIDL, the logic generated by the system is messy, and all classes are integrated together. However, after we have made it clear, it is relatively easy to understand. So here, let's write one ourselves, instead of using the system generated one.
First, define an interface to integrate IInterface, add add and addTask methods, and define the id of the method:
public interface IRemote extends IInterface { static final java.lang.String DESCRIPTOR = "com.example.ipcdemo.aidl.IRemote"; static final int TRANSACTION_add = (IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_addTask = (IBinder.FIRST_CALL_TRANSACTION + 1); //Sum of two numbers public int add(int num1, int num2) throws RemoteException; //Add a task public TaskInfo addTask(TaskInfo info) throws RemoteException; }
Then create an implementation class IRemoteLmpl with cross process, integrate Binder and implement IRemote interface:
public class IRemoteImpl extends Binder implements IRemote { @Override public int add(int num1, int num2) throws RemoteException { return 0; } @Override public TaskInfo addTask(TaskInfo info) throws RemoteException { return null; } @Override public IBinder asBinder() { return this; } }
Next, we should register the binder and string description into the service in the construction method of:
public IRemoteImpl() { this.attachInterface(this,DESCRIPTOR); }
The method asInterface to implement the AIDL object transformed by the server for the client to call:
public static IRemote asInterface(IBinder obj){ if (obj == null) { return null; } if (obj instanceof IRemote){ return (IRemote) obj; } return new Proxy(obj); }
The Proxy proxy class will be written later.
Next, write the specific implementation method onTransact of IRemote:
@Override protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException { //Corresponding method by code switch (code) { case TRANSACTION_add: data.enforceInterface(DESCRIPTOR); //Parameter data received int num1 = data.readInt(); int num2 = data.readInt(); int result = this.add(num1, num2); reply.writeNoException(); //Return the result to the return parameter reply.writeInt(result); return true; case TRANSACTION_addTask: data.enforceInterface(DESCRIPTOR); TaskInfo info = null; if (data.readInt() != 0) { //Get data through deserialization info = TaskInfo.CREATOR.createFromParcel(data); } TaskInfo resultInfo = this.addTask(info); reply.writeNoException(); if ((resultInfo!=null)) { reply.writeInt(1); info.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); } else { reply.writeInt(0); } return true; default: return super.onTransact(code, data, reply, flags); } }
There is no problem with the transformation of some data. Then, implement the proxy class to facilitate cross process calls:
/** * Cross process proxy class */ private static class Proxy implements IRemote { private IBinder mRemote; Proxy(IBinder iBinder) { mRemote = iBinder; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } @Override public IBinder asBinder() { return mRemote; } @Override public int add(int num1, int num2) throws RemoteException { Parcel data = Parcel.obtain(); Parcel replay = Parcel.obtain(); int reslut; try { data.writeInterfaceToken(DESCRIPTOR); data.writeInt(num1); data.writeInt(num2); /** * As you can see, when a cross process is called, it is only to run the parameters of the caller to its own method, and then return the result back */ mRemote.transact(TRANSACTION_add, data, replay, 0); replay.readException(); reslut = replay.readInt(); } finally { data.recycle(); replay.recycle(); } return reslut; } @Override public TaskInfo addTask(TaskInfo info) throws RemoteException { Parcel data = Parcel.obtain(); Parcel replay = Parcel.obtain(); TaskInfo result; try { data.writeInterfaceToken(DESCRIPTOR); if ((info != null)) { //Use data 0,1 to distinguish whether the write is successful data.writeInt(1); info.writeToParcel(data, 0); } else { data.writeInt(0); } mRemote.transact(TRANSACTION_addTask, data, replay, 0); replay.readException(); if ((0 != replay.readInt())) { result = TaskInfo.CREATOR.createFromParcel(replay); } else { result = null; } } finally { data.recycle(); replay.recycle(); } return result; } }
In this way, our code is finished
Next, modify the AIDL service for the RemoteService that is called by others:
//AIDL's services, IRemoteImpl mBinder = new IRemoteImpl() { @Override public int add(int num1, int num2) throws RemoteException { /** * Here is the specific implementation method, such as directly returning the sum of two numbers */ Log.d(TAG, "zsr add: Two numbers passed by the client were received: "+num1+" "+num2); return (num1 + num2); } @Override public TaskInfo addTask(TaskInfo info) throws RemoteException { Log.d("zsr", "Receive information from client: "+info); info.id = 0; info.progress = 50; // mHandler.sendEmptyMessage(1); // mTaskInfo = info; return info; } };
Then, copy the two methods to the client:
Bind server service:
//Bind AIDL service intent.setClassName("com.example.ipcdemo","com.example.ipcdemo.service.RemoteService");
class RemoteService implements ServiceConnection{ @Override public void onServiceConnected(ComponentName name, IBinder service) { // mBinder = IRemoteService.Stub.asInterface(service); mBinder = IRemoteImpl.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { } }
Then in the click event, call the AIDL method to get the data:
public void testAidl(View view) { try { if (mBinder != null) { TaskInfo info = new TaskInfo(); info.url = "www.google.com"; TaskInfo taskInfo = mBinder.addTask(info); int num = mBinder.add(2,3); Log.d(TAG, "zsr testAidl: Get server data: "+taskInfo+" "+num); } } catch (RemoteException e) { e.printStackTrace(); } }
Print as follows;
In this way, we can also achieve cross process communication without AIDL.
Project code: https://gitee.com/zhengshaorui/IpcDemo