AIDL
前面我们讲了,在使用 AIDL 编写 IPC 代码时,我们只需要编写简单的 接口 aidl 文件。
Make Project 后系统就会帮我们生成 Java 文件。
AIDL 生成文件分析
AIDL 帮我们生成内容:
可以看到,生成的接口 IMyAidl
继承了 IInterface
。IInterface
是进程间通信定义的通用接口。
同时 IMyAidl
中也包含了我们在 aidl 文件中声明的两个方法。
除此外,IMyAidl
中还包括一个抽象类 Stub
,它是一个 Binder
,实现了 IMyAidl
接口:
Stub
public static abstract class Stub extends android.os.Binder implements net.sxkeji.shixinandroiddemo2.IMyAidl { //唯一标识,一般为完整路径 private static final java.lang.String DESCRIPTOR = "net.sxkeji.shixinandroiddemo2.IMyAidl"; /** * 将当前接口与 Binder 绑定 */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * 将一个 IBinder 转换为 IMyAidl,如果不在一个进程就创建一个代理 */ public static net.sxkeji.shixinandroiddemo2.IMyAidl asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } //拿着标识从本地查询接口 android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof net.sxkeji.shixinandroiddemo2.IMyAidl))) { return ((net.sxkeji.shixinandroiddemo2.IMyAidl) iin); } //查不到就返回代理 return new net.sxkeji.shixinandroiddemo2.IMyAidl.Stub.Proxy(obj); } //覆盖 IInterface 的方法,获取当前接口对应的 Binder 对象 @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_addPerson: { //执行 addPerson 方法 data.enforceInterface(DESCRIPTOR); net.sxkeji.shixinandroiddemo2.bean.Person _arg0; if ((0 != data.readInt())) { //反序列化传入的数据 _arg0 = net.sxkeji.shixinandroiddemo2.bean.Person.CREATOR.createFromParcel(data); } else { _arg0 = null; } //调用 addPerson 方法,这个方法的实现是在服务端 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); } //不在一个进程时返回的代理 private static class Proxy implements net.sxkeji.shixinandroiddemo2.IMyAidl {...} //用于 onTransact 方法的两个 code,分别标识要进行的操作 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
的几个关键内容介绍:
构造函数
- 调用了
attachInterface()
方法 - 将一个描述符、特定的 IInterface 与当前 Binder 绑定起来,这样后续调用 queryLocalInterface 就可以拿到这个
- 需要创建一个
DESCRIPTOR
,一般是类的具体路径名,用于唯一表示这个 IInterface
- 调用了
asInterface()
- 将
IBinder
转换为IMyAidl
,这用于返回给客户端 - 不在一个进程的话,客户端持有的是一个代理
- 将
onTransact()
- Binder 关键的处理事物方法
- 根据传入的 code,调用本地/服务端的不同方法
其中可以看到,在不同进程时返给客户端的是代理。
Proxy
private static class Proxy implements net.sxkeji.shixinandroiddemo2.IMyAidl { private android.os.IBinder mRemote; //代理的远端 IBinder Proxy(android.os.IBinder remote) { mRemote = remote; } //获取代理的 Binder @Override public android.os.IBinder asBinder() { return mRemote; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } /** * 代理嘛,处理下数据后直接调用实际 Binder 来处理 */ @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); } //调用远端 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; } }
AIDL 生成的内容小结
IInterface
类型的接口,包括:
Stub
抽象类- aidl 接口定义的操作方法
Stub
,是一个Binder
,同时也是一个IInterface
,包括:
- 将 Binder 转成 IInterface 的
asInterface()
方法 - 处理调度的
onTransact()
方法 - 用于在
onTransact()
中标识要进行的操作的两个标志 - 一个
IInterface
类型的代理
- 将 Binder 转成 IInterface 的
Proxy
,IInterface
类型的代理,包括:
- 接口定义方法的伪实现,实际调用的是真正的 Binder 的方法
一句话总结:AIDL 帮我们生成了 Binder
和 跨平台接口的转换类 Stub
,以及在不同进程时,客户端拿到的代理 Proxy
。
现在回去看一下 AIDL 的使用,就会多了些理解。
AIDL 的使用回顾
服务端
使用时先在另一个进程的 Service 中实现 AIDL 生成文件中的 Stub
类,然后在 onBind()
中返回:
结合上面的分析,可以知道,我们在服务端实例化的是 Stub
的实体,它既是 Binder
也是 IInterface
。在其中实现了接口定义的方法,然后在 onBind()
中返回自己。
客户端
在 Activity 中使用 bindService()
绑定服务,然后再回调中,调用 Stub.asInterface()
将拿到的远端 Binder 转换为定义的接口,跨进程的话这里拿到的实际是代理接口:
然后就可以调用 Service 中方法了。
小结
根据上面的分析,我们可以看到,AIDL 帮我们做了以下几件事:
- 根据定好的接口生成不同进程都可以共同访问的接口类
- 在接口类中提供了 Binder 和接口的共同载体
Stub
- 在
Stub
中创建了代理类,用于映射调用实际接口实现
有了 AIDL,我们编写跨进程操作就变得十分简单,我们只需要关注业务接口的实现即可。
手动写个 Binder
我们可以模仿 AIDL 创建的文件,手动写个 Binder 来加深下理解。
首先是定义跨进程接口,实现 IInterface
在其中定义要跨进程做的操作,以及标识这两个操作的 code:
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; }
然后在其中创建这个接口与对应 Binder 的转换类 Stub
既然要两头讨好,那它就需要继承 Binder
的同时实现前面定义的接口,同时提供 Binder 和接口转换的方法,以及作为接口处理事物的方法:
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 线程池中的,因此可以直接使用同步的方式实现。