用过aidl的同学,可能见过下面的写法:
interface IInterface {
void foo0(in int input);
void foo1(out IDTParcel parcel);
void foo2(inout IDTParcel parcel);
}
不知道你有没有好奇过这里的 in / out / inout 是什么意思呢?
directional tag
去官网一查,只找到一点点信息:
All non-primitive parameters require a directional tag indicating which way the data goes. Either
in
,out
, orinout
(see the example below).Primitives,
String
,IBinder
, and AIDL-generated interfaces arein
by default, and cannot be otherwise.Caution: You should limit the direction to what is truly needed, because marshalling parameters is expensive.
哦,原来这里的 in / out / inout 属于 directional tag (定向标签?)的概念,指的是which way the data goes
(数据以哪种方式流动?),啥意思?从概念到解释都不是人话;不满意的你继续搜索相关博客…
directional tag 不是什么?
在说清楚它是什么之前,先聊聊directional tag不是什么:
如果你搜索aidl in out这几个关键词,会有很多文章出来,很多文章的结论是这样的:
AIDL中的定向 tag 表示了在跨进程通信中数据的流向
其中 in 表示数据只能由客户端流向服务端out 表示数据只能由服务端流向客户端inout 则表示数据可在服务端与客户端之间双向流通。
Stack Overflow上也一样:
Here it goes,
- Its only a directional tag indicating which way the data goes.
in - object is transferred from client to service only used for inputsout - object is transferred from client to service only used for outputs.inout - object is transferred from client to service used for both inputs and outputs.
(为了避免部分追求“效率”的读者只读关键词,文中错误的结论都会加中划线)
上面的结论听着很有道理,但你可能会发现一个问题:接口回调的场景无法实现了!
在aidl中,如果client向server注册一个Callback(如下代码所示),server会在某些场景回调client,这时候数据流向是server => client, 按照上面的逻辑,这个result数据无法到达client,因为int数据的directional tag只能是in(后面会讲到),
而in只能支持client到server的数据传输方向
//aidl file
interface ICallback {
void onResult(int result);
}
//aidl file
interface IController {
void registerCallback(ICallback callback);
}
但是,如果使用过AIDL,会发现接口回调是可以正常工作的(验证demo地址结果如下),否则我们早就发现这个高频使用场景的异常了。
D/directional tag: server register callback
D/directional tag: client onResult: 1
结论和事实有冲突,假设(上面的结论)一定有问题!
大家得出这个错误结论是情有可原的,毕竟对于大多数开发者,AIDL“听得多,用得少”,第一个人在写Demo验证的时候场景特殊,基于这个特殊场景得出的结论就是错误的。
其实这也是刺激我写下本文的原因,因为全网浏览量最高的博客(几乎)全都讲错了,真是生气又骄傲~
那么 directional tag 到底是什么呢?
下面我们就一步一步来验证:
源码之下
要弄清楚究竟发生了什么,源码之下毫无秘密。
为了避免部分同学一脸懵逼,这里补充一点关于AIDL的前置知识:
AIDL作为一种跨进程通信的方案,底层依赖Binder,跨进程通信时会调用AIDL中定义的方法,会把 caller(调用者,后文只用caller)的参数数据 copy 到 callee(接收者,后文只用callee),然后在callee进程中调用另外一个代理对象的相同方法,这个逻辑由Binder框架封装;使用者上层看起来,感觉是直接调用了对方进程中对象的方法。
AIDL文件在编译后会生成2个重要的实现类:
Stub
callee被调用时,会通过Stub.onTransact(code, data, reply, flag)间接地调用本地对象(Local Binder)的对应方法。Proxy
caller调用AIDL方法时,最终通过Proxy调用remote.transact(code, _data, _reply, flag),然后通过Binder机制调用到远程的相应方法。上面的onTransact() 和 transact() 方法都是Binder定义的方法,更底层的跨进程逻辑由Binder机制实现,就不是本文的重点了。
有了这些基础知识,下面我们写一个AIDL文件,看一下对应的方法做了什么事情,全部代码请看这里。
//aidl file: State
parcelable State;
//aidl file: IController
interface IController {
int transIn(in State state);
int transOut(out State state);
int transInOut(inout State state);
}
AIDL文件IController
编译后的关键代码如下:
in
//Proxy(caller)
public int