AIDL
AIDL的核心有两点
- AIDL是一种跨进程通讯方式
这种方式是基于Binder机制来进行的,Binder本质上是基于C/S架构,Service提供服务(方法),Client使用服务(方法调用) - AIDL本质上是一种代码生成的方式
从这一点上来说它和apt代码生成方式没有本质区别,AIDL的特殊之处在于它生成的代码是Binder进程间通讯的最外层封装。
AIDL特点
AIDL的语法和Java基本类似,但是也有不同点,主要在以下几点
- 支持的类型
- Parcelable和AIDL接口都需要导包
- 没有public等关键字
- 所有非基本类型的都需要添加in、out、inout关键字,用于指明数据流向,默认为in
支持的类型
默认情况下,AIDL 支持下列数据类型:
- Java 编程语言中的所有原语类型(如 int、long、char、boolean 等等)
- String
- CharSequence
- Parcelable Parcelable必须显示加入一个 import 语句,即使这些类型是在与接口相同的软件包中定义。
- List List 中的所有元素都必须是列表中支持的数据类型、其他 AIDL 生成的接口或Parcelable类型。 可选择将 List 用作“通用”类(例如,List
)。另一端实际接收的具体类始终是 ArrayList,但生成的方法使用的是 List 接口。 - Map
Map 中的所有元素都必须是列表中支持的数据类型、其他 AIDL 生成的接口或Parcelable类型。 不支持通用 Map(如 Map形式的 Map)。 另一端实际接收的具体类始终是 HashMap,但生成的方法使用的是 Map 接口。 - AIDL对应的接口
AIDL生成文件解析
AIDL最终会生成一个继承自IInterface的接口,先简单看下这个类的结构。这里我做了一些结构优化,看上去更方便一些
public interface IMusicControler extends IInterface{
public static abstract class Stub extends Binder implements IMusicControler{
private static final String DESCRIPTOR = "site.yihome.ipc.IMusicControler";
public static IMusicControler asInterface(IBinder obj){
......
}
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
......
return super.onTransact(code, data, reply, flags);
}
public static class Proxy implements IMusicControler{
private IBinder mRemote;
Proxy(IBinder remote) {
mRemote = remote;
}
@Override
public IBinder asBinder()
{
return mRemote;
}
@Override
public void startMusic(Music music) throws RemoteException {
......
}
@Override
public void stopMusic() throws RemoteException {
......
}
@Override
public void setMusicStateCallBack(IMusicStateCallBack cb) throws RemoteException {
......
}
}
}
public void startMusic(Music music) throws RemoteException;
public void stopMusic() throws RemoteException;
public void setMusicStateCallBack(IMusicStateCallBack cb) throws RemoteException;
}
从上面的代码中,我们可以明显的看到IMusicControler中以包名定义一个标示DESCRIPTOR,这个标示贯穿整个通讯过程,同时定义一个三层结构
- 最外层就是我们AIDL中定义的最原始的接口类型IMusicControler,和它定义的方法
- 第二层是一个抽象类Stub,他是三层中唯一的Binder对象,用于和Binder交互完成真正的进程间通讯
- 第三层是一个IMusicControler的实现类Proxy,和名字一样是Service端的代理类,进程间通讯时client首先调用的就是这个类的方法,然后在通过Binder调用远程对象。
如下图所示,Stub和Proxy本质是就是Binder两端的数据封装和解析层
Proxy主要完成Client调用时参数的转化,它把方法的参数转换成Parcel类型,用于Binder通信时传输数据,同时也把Binder传回的Parcel类型的返回值转换成对应我们需要的类型。
Stub和Proxy类似,但是Stub主要完成的是Server端的数据转化。具体的代码细节我们借助下面AIDL调用的全过程来讲解
AIDL调用全过程
Client调用过程
AIDL的调用过程,我们从ServiceConnection
的onServiceConnected
方法开始
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
musicControler = IMusicControler.Stub.asInterface(service)
......
}
一般来说,bindService
后在onServiceConnected
的回掉中,我们都会通过Stub的asInterface
方法获取可以操作的IInterface对象,asInterface
具体细节如下
public static IMusicControler asInterface(IBinder obj){
if(obj==null){
return null;
}
IInterface iIn = obj.queryLocalInterface(DESCRIPTOR);
if(iIn!=null&&(iIn instanceof IMusicControler)){
return (IMusicControler) iIn;
}
return new Proxy(obj);
}
在asInterface
中,首先通过DESCRIPTOR去查询本地接口(即非跨进程),如果是进程间通讯则会用这个IBinder对象去new一个Proxy对象。也就是说我们在Client中拿到的IMusicControler事实上就是Proxy。
Proxy(IBinder remote) {
mRemote = remote;
}
如果这时调用startMusic(Music music)
去调用远程服务,事实上是调用的Proxy的方法
public void startMusic(Music music) throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((music!=null)) {
_data.writeInt(1);
music.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(TRANSACTION_startMusic, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
可以看到Proxy事实上只是把参数封装到了Parcel对象中,同时会去获取reply中的数据。同时会远程服务IBinder的transact
,这个IBinder事实上是一个BinderProxy
对象。这里就会调用BinderProxy的transact
方法
public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
Binder.checkParcel(this, code, data, "Unreasonably large binder buffer");
......
try {
return transactNative(code, data, reply, flags);
} finally {
......
}
}
可以看到这个transact
方法最终会调用native方法通过Binder完成真正的跨进程。Client的流程到此结束。
Server端调用过程
在IPC的Service中,我们需要在onBind方法中需要返回一个Stub的实现类
return new IMusicControler.Stub() {......};
这个对象就是Server端的入口类,Client中transactNative
方法最终会调用Stub的onTransact
这个由它自己实现的方法
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code){
case INTERFACE_TRANSACTION:
reply.writeString(DESCRIPTOR);
return true;
case TRANSACTION_startMusic:
data.enforceInterface(DESCRIPTOR);
Music music;
if ((0!=data.readInt())) {
music = Music.CREATOR.createFromParcel(data);
}else {
music = null;
}
this.startMusic(music);
reply.writeNoException();
break;
......
}
return super.onTransact(code, data, reply, flags);
}
这个方法会对在transact
传入的code进行不同的处理,同时从Parcel中获取实际参数,最后调用Service中的对应方法。
AIDL异步调用
默认情况下AIDL的调用是同步的,即必须等到Server端执行完成后才会返回。如何实现异步调用呢,可以看到在前面的transact
中有四个参数,我们用了其中三个,而第四个参数flag正是用来标示调用方式的,默认0标示同步调用,如果需要异步调用,则需要把flag设置为FLAG_ONEWAY(这个过程想要用AIDL文件进行的话只需要在对应的方法前添加oneway关键字)
但是通过异步调用,我们是获取不到返回值的,因此需要接口回掉,但是普通的接口回掉无法满足跨进程通讯的需求,这种情况怎么处理呢?
还是Binder,前面一般情况下Service是充当Server端提供方法,Activity作为Client调用方法,Binder完成Client对Server的调用,当需要接口回掉时,两者身份就会反转,Activity作为Service提供IBinder对象,供Service调用,而这个IBinder对象则调用Service的方法通过参数传递(前面也说过,AIDL是支持这种IBinder对象传递的)
Binder的回掉方法是执行在子线程中的,这一点可能和Binder的机制有关,有待进一步研究
RemoteCallbackList
上面这一种异步回掉只是一种简单的情况,当一个Service需要对很多Client进行回掉时,就需要对注册的回掉进行一个系统的管理了,RemoteCallbackList正是用来处理这种情况的,它内部使用了一个ArrayMap来存储所有的回掉,同时会在需要的时候进行调用,核心的代码如下
public class RemoteCallbackList {
ArrayMap mCallbacks
= new ArrayMap();
public boolean register(E callback) {
return register(callback, null);
}
public boolean register(E callback, Object cookie) {
synchronized (mCallbacks) {
if (mKilled) {
return false;
}
// Flag unusual case that could be caused by a leak. b/36778087
logExcessiveCallbacks();
IBinder binder = callback.asBinder();
try {
Callback cb = new Callback(callback, cookie);
binder.linkToDeath(cb, 0);
mCallbacks.put(binder, cb);
return true;
} catch (RemoteException e) {
return false;
}
}
}
public boolean unregister(E callback) {
synchronized (mCallbacks) {
Callback cb = mCallbacks.remove(callback.asBinder());
if (cb != null) {
cb.mCallback.asBinder().unlinkToDeath(cb, 0);
return true;
}
return false;
}
}
public void broadcast(Consumer action) {
int itemCount = beginBroadcast();
try {
for (int i = 0; i < itemCount; i++) {
action.accept(getBroadcastItem(i));
}
} finally {
finishBroadcast();
}
}
}
这个类并不复杂,它主要封装了对callback常见的操作,同时帮助我们做了很多线程同步上的工作。
下步目标
了解Binder通讯原理