AIDL
As we mentioned earlier, when using AIDL to write IPC code, we only need to write a simple interface aidl file.
After Make Project, the system will help us generate Java files.
AIDL Makefile Analysis
AIDL helps us generate content:
As you can see, the generated interface is IMyAidl
inherited IInterface
. IInterface
is a generic interface defined for inter-process communication.
It IMyAidl
also includes the two methods we declared in the aidl file.
In addition to that, IMyAidl
there is an abstract class Stub
, which is an abstract class, Binder
that implements the IMyAidl
interface:
Stub
public static abstract class Stub extends android.os.Binder implements net.sxkeji.shixinandroiddemo2.IMyAidl { //Unique identifier, usually the full path private static final java.lang.String DESCRIPTOR = "net.sxkeji.shixinandroiddemo2.IMyAidl"; /** * Bind the current interface to Binder */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Convert an IBinder to IMyAidl, create an agent if not in a process */ public static net.sxkeji.shixinandroiddemo2.IMyAidl asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } / / Take the identity to query the interface from the local android.os.IInterface iin = obj.queryLocalInterface (DESCRIPTOR); if (((iin != null) && (iin instanceof net.sxkeji.shixinandroiddemo2.IMyAidl))) { return ((net.sxkeji.shixinandroiddemo2.IMyAidl) iin); } //Return to proxy if not found return new net.sxkeji.shixinandroiddemo2.IMyAidl.Stub.Proxy(obj); } //Override the method of IInterface to get the Binder object corresponding to the current interface @Override public android.os.IBinder asBinder() { return this; } //Key method, handle operation, return @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: { //Get the descriptor of the current interface reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_addPerson: { //Execute the addPerson method data.enforceInterface(DESCRIPTOR); net.sxkeji.shixinandroiddemo2.bean.Person _arg0; if ((0 != data.readInt())) { //Deserialize incoming data _arg0 = net.sxkeji.shixinandroiddemo2.bean.Person.CREATOR.createFromParcel(data); } else { _arg0 = null; } //Call the addPerson method, the implementation of this method is on the server side this.addPerson(_arg0); reply.writeNoException(); return true; } case TRANSACTION_getPersonList: { data.enforceInterface(DESCRIPTOR); java.util.List<net.sxkeji.shixinandroiddemo2.bean.Person> _result = this.getPersonList(); reply.writeNoException(); reply.writeTypedList(_result); return true; } } return super.onTransact(code, data, reply, flags); } // proxy to return when not in a process private static class Proxy implements net.sxkeji.shixinandroiddemo2.IMyAidl {...} //Two codes for the onTransact method, respectively identifying the operation to be performed static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_getPersonList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); }
Stub
Several key contents of the introduction:
构造函数
attachInterface()
method called- Bind a descriptor, a specific IInterface to the current Binder, so that subsequent calls to queryLocalInterface can get this
- You need to create one
DESCRIPTOR
, usually the specific path name of the class, to uniquely represent this IInterface
asInterface()
- will be
IBinder
converted toIMyAidl
, which is used to return to the client - If it is not in a process, the client holds a proxy
- will be
onTransact()
- Binder's key way of handling things
- According to the incoming code, call different methods on the local/server side
It can be seen that the proxy is returned to the client in different processes.
Proxy
private static class Proxy implements net.sxkeji.shixinandroiddemo2.IMyAidl { private android.os.IBinder mRemote; //Remote IBinder of the agent Proxy(android.os.IBinder remote) { mRemote = remote; } //Get the Binder of the proxy @Override public android.os.IBinder asBinder() { return mRemote; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } /** * As an agent, after processing the data, directly call the actual Binder to process it */ @Override public void addPerson(net.sxkeji.shixinandroiddemo2.bean.Person person) 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 ((person != null)) { _data.writeInt(1); person.writeToParcel(_data, 0); } else { _data.writeInt(0); } //call remote mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } @Override public java.util.List<net.sxkeji.shixinandroiddemo2.bean.Person> getPersonList() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.util.List<net.sxkeji.shixinandroiddemo2.bean.Person> _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_getPersonList, _data, _reply, 0); _reply.readException(); _result = _reply.createTypedArrayList(net.sxkeji.shixinandroiddemo2.bean.Person.CREATOR); } finally { _reply.recycle(); _data.recycle(); } return _result; } }
Summary of AIDL-generated content
IInterface
types of interfaces, including:
Stub
abstract class- Operation methods defined by aidl interface
Stub
, is oneBinder
, and is also oneIInterface
, including:
asInterface()
The method of converting Binder to IInterfaceonTransact()
Methods to handle schedulingonTransact()
Two flags used to identify the action to be taken in- a
IInterface
type of proxy
Proxy
,IInterface
types of proxies, including:
- The pseudo-implementation of the interface definition method, the actual call is the real Binder method
One sentence summary: AIDL helps us generate Binder
conversion classes with cross-platform interfaces Stub
, as well as proxies obtained by clients in different processes Proxy
.
Now go back and look at the use of AIDL, and you will understand a little more.
A review of the use of AIDL
Server
When using, first implement the classes in the AIDL generated file in the Service of another process Stub
, and then onBind()
return in:
Combined with the above analysis, we can know that what we instantiate on the server is Stub
the entity, it is Binder
both IInterface
. It implements the methods defined by the interface in it, and then onBind()
returns itself in it.
client
Use the binding service in the Activity bindService()
, and then call in the callback to Stub.asInterface()
convert the obtained remote Binder into the defined interface. If it is cross-process, what is actually obtained here is the proxy interface:
Then you can call the method in the Service.
summary
According to the above analysis, we can see that AIDL helps us do the following things:
- Generate interface classes that can be accessed by different processes according to the predetermined interface
- The common carrier of Binder and interface is provided in the interface class
Stub
- Created a proxy class in
Stub
the mapping call to the actual interface implementation
With AIDL, it becomes very simple for us to write cross-process operations, we only need to focus on the implementation of the business interface.
Manually write a Binder
We can imitate the files created by AIDL and manually write a Binder to deepen our understanding.
The first is to define a cross-process interface and implement IInterface
In it, define the operations to be done across processes, and the code that identifies these two operations:
public interface IMyAidlDiy extends IInterface { static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_getPersonList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); public void addPerson(Person person) throws RemoteException; public List<Person> getPersonList() throws RemoteException; }
Then create this interface and the corresponding Binder conversion class in it Stub
Since it wants to please both ends, it needs to inherit Binder
and implement the interface defined earlier, and also provide methods for Binder and interface conversion, as well as methods for handling things as an interface:
public static abstract class Stub extends Binder implements IMyAidlDiy { private static final String DESCRIPTOR = "net.sxkeji.shixinandroiddemo2.activity.ipc.IMyAidlDiy"; public Stub() { attachInterface(this, DESCRIPTOR); } @Override public IBinder asBinder() { return this; } public static IMyAidlDiy asInterface(IBinder binder){ if (binder == null){ return null; } IInterface localInterface = binder.queryLocalInterface(DESCRIPTOR); if (localInterface != null && localInterface instanceof IMyAidlDiy){ return (IMyAidlDiy) localInterface; }else { return new Stub.Proxy(localInterface); } } @Override protected boolean onTransact(final int code, final Parcel data, final Parcel reply, final int flags) throws RemoteException { switch (code){ case TRANSACTION_addPerson: data.enforceInterface(DESCRIPTOR); Person _arg0; if (data.readInt() != 0){ _arg0 = Person.CREATOR.createFromParcel(data); //反序列化参数 }else { _arg0 = null; } this.addPerson(_arg0); reply.writeNoException(); return true; case TRANSACTION_getPersonList: data.enforceInterface(DESCRIPTOR); List<Person> personList = this.getPersonList(); reply.writeNoException(); reply.writeTypedList(personList); break; } return super.onTransact(code, data, reply, flags); } }
最后创建代理接口,在不同进程中,客户端持有的是代理
它的作用就是伪装成真的 Binder,实际被调用时将数据处理成 Parcel,然后让被代理的 Binder 去处理:
private static class Proxy implements IMyAidlDiy { private IBinder mRemote; public Proxy(final IBinder obj) { mRemote = obj; } public java.lang.String getInterfaceDescriptor() { //伪装的和真的 Binder 名一样 return DESCRIPTOR; } @Override public void addPerson(final Person person) throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); if (person != null) { _data.writeInt(1); person.writeToParcel(_data, 0); } else { _data.writeInt(0); } mRemote.transact(TRANSACTION_addPerson, _data, _reply, 0); //这里调用实际的实现 _reply.readException(); } finally { _data.recycle(); _reply.recycle(); } } @Override public List<Person> getPersonList() throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); List<Person> _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(TRANSACTION_getPersonList, _data, _reply, 0); _reply.readException(); _result = _reply.createTypedArrayList(Person.CREATOR); } finally { _reply.recycle(); _data.recycle(); } return _result; } @Override public IBinder asBinder() { return mRemote; } }
自己写完实现对整个 Binder 实现的跨进程调用流程是否理解更深了呢。总结下:
需要注意的是,客户端在发起远程请求时,当前线程会被挂起直到服务端返回,因此尽量不要在 UI 线程发起远程请求。
而在服务端,Binder 方法是运行在 Binder 线程池中的,因此可以直接使用同步的方式实现。