Binder framework层源码:http://aosp.opersys.com/xref/android-10.0.0_r46/xref/frameworks/native/libs/binder/
SurfaceFlinger的Binder注册逻辑:http://aosp.opersys.com/xref/android-10.0.0_r46/xref/frameworks/native/services/surfaceflinger/main_surfaceflinger.cpp

继承关系

屏幕快照 2020-05-28 下午8.33.06.png

接口概括

IBinder

IBinder定义了Binder协议的基础接口方法,包括:

  • linkToDeath,允许BpBinder注册Binder死亡监听,当BBinder所属进程死亡时能够收到通知,及时释放资源;
  • queryLocalInterface,检查binder是否是一个BBinder且是特定IInterface接口对象;
  • localBinder / remoteBinder,IBinder默认实现了这两个方法,返回NULL,然后BBinder重写了localBinder返回this,BpBinder重写了remoteBinder,也返回this;
  • transact:BpBinder重写transact以通过Binder驱动和BBinder通信,BBinder重写transact以接受和处理从BpBinder发来的消息,BBinder还定义了onTransact,业务BnXXX类一般通过重写onTransact来处理自己定义的业务消息;
  • attachObject/detachObject:用来将指针信息和bidner对象关联,JNI调用Binder时,C++层的Binder会将Java对象地址记录在这里;

IInterface

IInterface定义了asBinder方法和asInterface方法,它们都是class static方法,用于将一个IInterface转为IBinder使用,或者用于将一个IBinder转为IInterface使用,本质上,IBinder是具体消息如何传递的接口,而IInterface是业务对于消息的封装。

  • asBinder:如果是BpInterface,会返回一个BpBinder对象;如果是BnInterface,则会返回一个BBinder对象;
  • asInterface:使用Binder接口时,必须利用IInterface提供的宏来定义一个要被BpBinder和BBinder继承的类,asInterface就是由宏来声明并定义的,这个方法会检查IBinder是BBinder还是BpBinder,这是通过queryLocalInterface来检查的,只有BBinder的子类通过继承BnInterface来获得了经过重写的queryLocalInterface方法,会返回BnInterface自身,但BpBinder的queryLocalInterface会直接使用IBinder默认的方法实现,返回null,这是asInterface会相信IBinder一定是一个BpBinder,利用它的remoteBinder构造一个BpInterface;

总的来看,这些接口都实现的比较trick,用到了大量技巧:

  • 类模板作为被继承的类;
  • 令Native和Proxy两侧的对象符合共同的父接口,然后再让Native和Proxy以不同的方式实现接口方法,从而能够以同一套逻辑处理两种类,在需要的时候也能够通过调用规定好的方法区分它们,这从设计上看非常难受,没有做到将细节隐藏在接口定义之下,在封装性这一条上失败了,但它能work;

设计分析

对C++模板的应用

BnInterface和BpInterface这两个类的声明看起来很奇怪,它们居然继承自模板类型,这充分利用了C++模板的实质:模板只是编译时代码生成,生成后的代码会被编译,若能编译则模板是合法的,若不能编译则模板是非法的。在BnInterface和BpInterface中,都假定作为父类的模板是继承自IInterfac的,并为BnInterface和BpInterface定义了在IInterface中声明的方法,若模板不继承自IInterface,那么模板类就没有声明这些方法,定义就是非法的,会在编译时报错。

C-S模式

在Android系统上,很多系统服务都运行在独立的进程,它们对其他进程提供服务接口,总体上看是Client-Server模式,这里通信用到的协议就是由Binder机制支持的。

发布Server

系统服务都有固定的名称,当服务启动时,服务会做一些基本的初始化工作,让自己处于一个准备好提供服务的状态,然后将自己注册给Binder。
以上面贴出的SurfaceFlinger为例,它的注册逻辑位于SF服务的入口函数main里,通过ISurfaceManager#addService方法,注册了一个名为“SurfaceFlinger”的服务,addService接受的服务类型是IBinder,实际上传入的是BnBinder。这里用到的ISurfaceManager对象本身也是一个通过Binder机制提供的服务,但它比较特殊,是0号Binder服务,所有其他服务都能直接和ISurfaceManager通信,但其他服务要想被其他进程使用,必须将自己注册给ISurfaceManager。

Client找到Server

Client是通过ServiceManager找到服务的,ServiceManager会返回Client一个IBinder对象,这里的对象实际上可能是BnBinder,也可能是BpBinder,取决于Client当前所处的进程,但一般都是BpBinder,因为Client一般不会和Server位于同一个进程。
当拿到服务的IBinder对象后,需要将这个IBinder进行包装,用服务发布的Binder Client接口提供的asInterface方法可以将IBinder包装成服务接口对象,当Client调用任何服务接口时,实际上是在向IBinder对象写入调用指令数据,这些数据被写入一个Binder文件,通过Binder驱动被传输到另一段的服务端,被服务端通过Binder文件读取出来,并进行处理。处理完毕后可能需要将返回数据回传给Client,也可能不需要,此外处理时也可能修改参数对象内容,客户端也可能需要了解服务的修改,这些细节可以通过手动编码来实现,但多数情况下代码都是通过IDL语言生成的,代码生成工具会读取in/out关键字以及检查方法是否有返回,来生成相应的实现代码。

匿名Binder

虽然Binder设计上是为了支持C-S模式,但是也存在匿名Binder,所谓匿名Binder就是即不是由App导出的Service Binder,也不是由系统服务启动时注册的Service Binder,而是在一个Binder调用发生时,由一端通过Binder方法调用的参数传递给另一端的Binder对象,这种Binder对象有时候是作为回调被另一端使用的,有时候是作为代表Client的一个token被另一端用作某些数据结构的索引的。

由内核层面支持的设计

Binder机制是由一个内核模块:Binder驱动来支撑的。在操作系统的视角,一切都是文件,驱动就是规定文件类型和行为,负责和操作系统内核合作实现文件接口的幕后苦工。
想要发布接口的服务,打开一个Binder文件,并通过ioctl进行一些配置,就可以将服务注册到Binder驱动内,对应一个token,后续其他服务想要使用该服务时,只要获得这个token即可,其中ServiceManager就是负责记录每个服务的名称和token的关系的;
想要使用接口的客户,先使用0号token建立和ServiceManager的Binder通道,然后向ServiceManager查询某个名称的服务的token,拿到token后,就可以建立和目标服务的Binder通道了,然后,利用服务提供的接口封装类对Binder进行包装,就可以确保通过封装后的接口发送过去的Binder指令是对方能接收的合法指令。

HIDL和AIDL

IDL,指的是Interface Definition Language,在Android系统中,IDL就是指Binder。其中H指的是Hardware,A指的是Application。由于硬件相关的代码都是C/C++的,而应用代码都是Java的,因此HIDL指的是C++ Binder通信接口定义,AIDL指的是Java Binder通信接口定义。

跨进程的引用计数

Binder机制引入了跨进程的引用计数问题,binder驱动内部维护着每一个进程的所有Binder对象映射,依靠这个映射来将binder驱动内部的binder_ref对象和不同进程内的BBinder、BpBinder关联上。因此,Binder引用计数涉及两个方面:引用程序的IBinder的引用计数管理,和驱动内的binder_ref引用计数管理。
// todo 这块有点复杂,aosp也没什么这方面的说明,之后再看看,总之Binder引用计数是跨进程的,但这也意味着一个进程内如果创建了一个Binder对象并传递给另一个进程,即使当前进程业务代码已经不再引用Binder对象,这个Binder对象也会被Binder机制暂时保护着,直到另一个进程也释放了该Binder对象(另一端是一个BpBinder),本进程内对应的这个Binder对象才能被释放。