Binder:为什么要通过onTransact()调用目标方法

转载自:http://www.jianshu.com/p/b260051237fe

 

Binder:为什么要通过onTransact()调用目标方法

 

0x00 背景

最近被提出一串问题:为什么android.os.Binder要提供onTransact()方法给子类重写。为什么要通过Client:invokeMethod -> onTransact() -> Service:targetMethod这一曲折过程来调用一个远程方法,为什么不能直接指定方法名称来调用。

这些问题阐述了同一个疑问:对于调用者而言,目标方法为何远在天边。

实际上,这并不是一个无关紧要的问题。尝试解答此问题有助于了解API设计者的初衷,以便从不同角度探究可能潜在的问题。了解系统创作者的思想,从而更好地使用其设计的接口及系统特性,避免出现错误决策导致长期迭代中难以维护。

本文假设你已具备IPC(进程间通信)开发经验。

0x01 AIDL 与 onTransact()

做一些简单回顾:

首选,无论定义于何处,.aidl文件始终是生成目标.java文件的声明标记。大多数情况下,对android.os.IInterfaceandroid.os.Binder的继承及实现不应当手动操作。所有的生成行为应当由.aidl声明来完成。此外,所生成的目标文件位于app/build/generated/source目录下。

其次,android.os.Binder实现于android.os.IBinderandroid.os.Binder实现了大多数进程状态所必要的功能,以及所有必要的功能调度。

.aidl存在的目的就是为了剔除大量重复性的工作。这其中包括,所生成目标文件下的类种类Stub方法:onTransact(int, android.os.Parcel, android.os.Parcel, int)。此方法重写自android.os.Binder。此外,.aidl是为对外暴露接口而设计的。

0x02 onTransact() 干了些什么

Binder.onTransact()是为Binder.transact()的调用而准备的。Binder.transact()做了两件事:

public final boolean transact(int code, Parcel data, Parcel reply,
        int flags) throws RemoteException {
    if (data != null) {
        data.setDataPosition(0);
    }
    boolean r = onTransact(code, data, reply, flags);
    if (reply != null) {
        reply.setDataPosition(0);
    }
    return r;
}

此段代码位于android/os/Binder.java

首先,在调用Binder.onTransact()之前及之后,分别对请求结构的引用及返回结构的引用重置读写position,以及调用Binder.onTransact()。在此提醒,Binder.transact()的调用者是Stub下的内部类Proxy中的各个.aidl中定义的方法。

最终,千辛万苦地,终于来到了.aidl自行生成实现的Binder.onTransact()方法了。特别的是,有两个地方值得去注意:

  • 其一,在最终的开发中,将会继承抽象类Stub,并实现所有在.aidl中定义的方法。这些具体方法的直接调用者,正是当前我们所在的onTransact()方法;

  • 其二,正是基于上面一条,可以得知:无论远程调用者(Client)身处何方,最终,一定会经过此处的onTransact()方法,并由onTransact()直接调用目标方法。

要知道,传入onTransact()方法的参数中,拥有目标方法的ID、指向参数的引用,以及指向返回结果的引用。所有远程调用者(Client)想要做的事,都通过层层调用及参数包装汇聚到onTransact(),再由onTransact()分发到真正的目标方法执行。

那么问题来了。为什么?

0x03 关键问题:进程隔离

一个简洁明了的回答是:除了预先定义的接口,其余的一切实现在进程间均相互不可见。

现在,以更直观的方式来展示调用者(Client)与服务(Service)间的关系:


上下层关系

显然,所有的Client亦或是Service,都是平级的。原因显而易见:这部分运行时程序,都是基于Android Framework开发的。

那么,如果A程序自行定义了接口,B程序怎样知道A程序定义了接口?换言之:B程序该通过什么方式来查找A程序中的自定义接口以至调用?显然,所有基于Android Framework开发的程序,都不存在上下级关系。

同时,由于虚拟机相互独立,因此这些程序并不在同一个运行时中。两两之间相隔一堵不透明的墙,它们唯一可见的,就是下层的Binder元素。

答案就此基本浮出水面。Client与Service的状态是不可预知的,使用Binder Driver隐藏进程间调用细节,并通过Binder.onTransact()分发调用指令,最终在参数引用中写入计算结果——这一过程实现了设计模式中的简易命令模式。整个进程间调用作用于Binder Driver,至于Binder.onTransact()格外引人瞩目,则是因为它是整个过程的末端操作。

正如上图所示,把所有请求汇聚到onTransact(),具体需要请求哪个方法,则抽象为id处理。另外,所有目标方法的请求参数及返回体都要求是基本类型或被.aidl所定义的。这意味着在传输过程中所有信息都被视作“流”来处理。

0x04 更多思考

理解Binder调度过程有助于设计更易于维护的接口——尤其是库。某些需求的实现可能需要对ProxyStub进行手动编辑,此时理解API设计者的意图显得极为重要。

毕竟,维护那些凭直觉写出的代码,简直就是灾难。



作者:Abel_Joo
链接:http://www.jianshu.com/p/b260051237fe
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
asii
2楼 · 2016.07.29 17:24

说到了重点 使用了命令模式 其实aidl生成的java类很值得我们参考 本身是一个代理模式的实现  而他的子类才是实现各个操作的真正实现者 这里用到了模板模式  这些底层模块的建设处处蕴含着设计者对DIP设计原则的理解

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值