Binder是Android提供给我们的一种跨进程通信方案,Android虽然说是基于Linux内核的,可以使用Linux的管道进行跨进程通信,也可以使用网络的方法(使用Socket)进行跨进程通信,但是这些方式都没有Binder方便和灵活。
从宏观的角度来看,Android可以看作是一个基于Binder通信的C/S架构。Binder在里面起到了一个网络的作用,它将Android系统的各个部分连接在了一起。
Native Binder 总体架构
在基于Binder通信的C/S架构体系中,除了C/S架构所包括的Client端和Server端外,Android还有一个全局的ServiceManager端,它的作用是管理系统中的各种服务(Service)。这三者之间的关系如下图所示:
注意,一个Server进程可以注册多个Service
上图中三者的先后关系是这样的:
- Server进程要先注册一些Service到ServiceManager中,所以Server是ServiceManager的客户端,两者之间的C/S关系为,Server对应客户端(Client),ServiceManager对应服务端(Server)
- 如果某个Client进程要使用某个Service,必须先到ServiceManager中获取该Service的相关信息,所以Client是ServiceManager的客户端,两者之间的C/S关系为,Client对应客户端(Client),ServiceManager对应服务端(Server)
- Client根据得到的Service信息与Service所在的Server建立通信,然后就可以直接与Service交互了,所以Client也是Service的客户端,两者之间的C/S关系为,Client对应客户端(Client),Server对应服务端(Server)
这三者之间交互全部都是基于Binder通信的。
Binder只是为这种C/S架构提供了一种通信方式,我们也完全可以次啊用其他IPC方式进行通信,实际上,系统中有很多其他的程序就是采用Socket或者Pipe(管道)方法进行进程间通信。ServiceManager并没有使用BpXXX和BnXXX
MediaServer
MediaServer以下均称为MS。
MS是一个可执行程序,虽然Android的SDK提供Java层的API,但Android系统本身还是一个完整的基于Linux内核的操作系统,所以并非所有的程序都是用Java编写的,MS就是一个使用C++编写的可执行程序。
MS是系统诸多重要Service的栖息地,它们包括了:
- AudioFlinger:音频系统中的核心服务。
- AudioPolicyService:音频系统中关于音频策略的重要服务。
- MediaPlayerService:多媒体系统中的重要服务
- CameraService:有关摄像/拍照的重要服务
可以看到,MS除了不涉及Surface系统外,其他重要的服务基本上都设涉及了。
MS是一个可执行程序,入口函数为main,写在Main_MediaServer.cpp当中
1 | int main(int argc,char **argv) |
上面的代码中,确定了个关键点,我们通过对这五个关键点的分析,来认识和理解Binder。
获得一个ProcessState实例:sp proc(ProcessState::self());
- 单例的ProcessState
ProcessState的代码在ProcessState.cpp中
1 | sp<ProcessState> PeocessState::self() { |
self函数采用了单例模式,根据这个以及ProcessState的名字很明确地告诉我们了一个信息:每个进程只有一个ProcessState对象,这一点,从它的命名中也可以看出端倪。
- ProcessState的构造
再来看Process的构造很熟,这个函数非常重要,它悄悄地打开了Binder设备。代码如下所示,在ProcessState.cpp中可以找到:
1 | ProcessState::ProcessState() |
- open_driver:打开binder设备
open_driver的作用就是打开/dev/binder这个设备,它是Android在内核中为完成进程间通信而专门设置的一个虚拟设备,具体实现如下所示,在ProcessState.cpp中:
1 | static int open_driver() { |
对于Process::self函数的作用总结如下:
- 打开dev/Binder设备,相当于与内核的Binder驱动有了交互的通道
- 对返回的fd使用mmap,这样Binder驱动就会分配一块内存来接收数据。
- 由于ProcessState具有唯一性,因此一个进程只打开设备一次
sp sm = defaultServiceManager();:调用defaultServiceManager,得到一个IServiceManager。
defaultServiceManager函数的实现在IServiceManager.cpp中完成。它会返回一个IServiceManager对象,通过这个对象,我们可以神奇地与另一个进程ServiceManager进行交互。
- defaultServiceManager调用的函数
defaultServiceManager的代码藏在IServiceManager中,如下:
1 | sp<IServiceManager> defaultServiceManager() { |
这里可以看到,gDefaultServiceManager是调用了ProcessState的getContextObject函数来赋值的,getContextObject函数在ProcessState.cpp文件中,如下所示:
1 | sp<IBinder> ProcessState::getContextObject(const sp<IBinder> &caller) { |
getStrongProxyForHandle的函数实现如下:
1 | sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle) |
上述代码中出现的BpBinder和它的孪生兄弟BBinder都是Android中与Binder通信相关的代表,他们都是从IBinder类中派生而来。
关于BpBinder和BBinder的关系可以这么理解:
- BpBinder是客户端用来与Server交互的代理类,p即Proxy的意思
- BBinder则是与proxy相对的一端,它是proxy交互的目的端,可以这么理解,BpBinder为客户端,那么BBinder则是对应的服务端,BpBinder和BBinder是一一对应的,某个BpBinder只能与对应的BBinder交互。
BpBinder是由Binder系统通过handler来标识对应的BBinder。
注意,我们给BpBinder构造函数传的参数handle的值是0,这个0在整个Binder系统中有重要含义–因为0代表的就是ServiceManager所对应的BBinder。
下面是BpBinder的代码,卸载BpBinder.cpp中:
1 | BpBinder::BpBinder(int32_t handle) |
可以看到,BpBinder和BBinder两个类没有任何地方操作ProcessState打开的那个/dev/binder设备
所以这段代码中
1 | gDefaultServiceManager = interface_cast<IServiceManager> { |
这里的 interface_cast函数就显得十分重要
interface_cast的具体实现如下
1 | templaate<typename INTERFACE>> |
这里仅仅是一个模板函数,真实的实现其实是下面这样的。
1 | inline sp<IServiceManager> interface_cast(const sp<IBinder> &obj) |
所以这个函数真正的实现还是在IServiceManager中的的,IServiceManager定义了ServiceManager中所提供的服务。
那么interface_cast是如何把BpBinder指针转换成一个IServiceManager指针的呢?答案就是下面这段代码。
1 | intr = new BpServiceManager(obj); |
由代码当中可以看出,interface_cast不是指针的转换,而是利用BpBinder对象作为参数新建了一个BpServiceManager对象。到这里我们已经看到了IServiceManager和BpServiceManager的身影,所有的类关系图谱如下:
从这张类图中我们可以看到BnServiceManager是同时继承了IServiceManager和BBinder,所以它可以直接地与Binder交互,但是BpServiceManager则没有直接继承关于Binder的类,它是通过BpRefBase与Binder进行交互的,因为BpRefBase中mRemote的值就是BpBinder。
再回到最开始的这段代码
1 | sp<IServiceManager> sm = defaultServiceManager(); |
通过defaultServiceManger函数,我们可以得到两个关键的对象:
- 一个BpBinder对象,它的handle值是0
- 有一个BpServiceManager对象,它的mRemote值是BpBinder
defaultServiceManager()实际返回的对象是BpServiceManager
初始化多媒体系统的MediaPlayer服务:MediaPlayerService::Instantiate();
MediaPlayerService的代码如下所示:
1 | void MediaPlayerService::instance() { |
因为defaultServiceManager实际返回的对象是BpServiceManager,所以实际的代码是这样的:
1 | vitual status_t addService(const String16 & name, const sp<IBinder> & service) |
很明显,addService函数就做了一件事情:就是把请求数据打包成data后,传给了BpBinder的transact函数,把通信的工作交给了BpBinder去完成。
BpBinder 的transact函数实现如下所示:
1 | status_t BpBinder::transact(uint32_t code, const Parcel & data, Parcel *reply, uint32_t flag) |
这里可以看出来,BpBinder把transact工作马上交给了IPCThreadState。现在我们来分析以下IPCThreadState与transact()
- IPCThreadState
IPCThreadState的实现代码如下:
1 | IPCThreadState *IPCThreadState::self() |
它的构造函数如下:
1 | IPCThreadState::IPCthreadState() |
每个线程都有一个IPCThreadState,每个IPCThreadState中都有一个mIn,一个mOut,其中,mIn是用来接收来自Binder设备的数据的,而mOut则是用来存储发往Binder设备的数据的。
- transact()
这个函数实际完成了与Binder通信的工作,如下代码所示:
1 | //注意,handle的值为0,代表通信的目的端 |
这个流程很简单:先发数据,然后等结果。
writeTransaction函数的作用是把请求命令写在mOut中了,handle的值为0,用来标识目的端,其中0是ServiceManager的标志。
waitForResponse函数的作用是发送请求和接收回复。收到回复后,就会调用talkWithDriver函数,在talkWithDriver函数中就会调用ioctl方式与Binder交互。
ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。
ProcessState::self()->startThreadPool():创建线程池
startThread的实现如下面代码所示:
1 | void PeocessState::startThreadPool() |
上面的spawnPooledThread函数的实现如下所示:
1 | void PeocessState::spawnPooledThread(bool isMain) |
可以看到,这里又新建了一个线程并运行,而这个新创建的线程(PoolThread)也调用了joinThreadPool这个函数。
IPCThreadState::self()->joinThreadPool();
具体实现如下:
1 | void IPCThreadState::joinThreadPool(bool isMain) |
原来,这里也使用了talkWithDriver,两个调用了joinThreadPool的线程看看从Binder设备那里能不能找点可做的事情。
MediaPlayerService总结
目前看来MediaPlayerService调用了两个线程为Service服务,分别是:
startThreadPool中新启动的线程(即第四步:ProcessState::self()->startThreadPool())通过joinThreadPool读取binder设备,查看是否有请求。
主线程也调用joinThreadPool读取binder设备,查看是否有请求。看来,binder设备是支持多线程操作的。
MediaPlayerService总共注册了四个服务。
Binder体系中通信层和业务层的交互关系可以通过这个图来表示:
服务总管ServiceManager
ServiceManager的原理
defaultServiceManager返回的是一个BpServiceManager,通过它可以把命令请求发送给handle值为0的目的端。而这些请求都被ServiceManger处理了。
ServiceManger的入口函数
ServiceManager的入口函数如下所示。
1 | int main(int argc, char **argv) |
- 打开binder设备:binder_open
binder_open函数用于打开binder设备,它的实现如下所示:
1 | /* |
- 成为manager:binder_become_context_manager(bs)
manager的实现如下面代码所示:
1 | int binder_become_context_manager(struct binder_state *bs) |
- 处理客户端发过来的请求:binder_loop
binder_loop函数代码如下所示:
1 | /* |
binder_handler指针func实际上就是svcmgr_handler,svcmgr_handler会使用一个switch/case语句调用对应的IServiceManagerManager中定义的各个业务函数,其中有一个业务函数为addService,这个函数主要的作用是判断注册服务的进程是否有权限,如果进程的用户组是root用户或system用户才允许注册,如果达不到root或system权限的进程,则需要在allowed结构数据中添加相应的项目。它的定义大概像下面这样:
1 | static struct{ |
总结一下,ServiceManager不过就是保存了一些服务的信息。
ServiceManager存在的意义
ServiceManager能集中管理系统内的所有服务,它能施加权限控制,并不是任何进程都能注册服务的。
ServiceManager支持通过字符串名称来查找对应的Service。这个功能很像DNS。
由于各种原因的影响,Service进程可能生死无常,如果让每个Client都去检测,压力实在太大了。现在有了统一的管理机构,Client只需要查询ServiceManager,就能把我动向,得到最新信息。
MediaPlayerService和它的Client
前面一直讨论ServiceManager和它的Client,现在我们以MediaPlayerService的Client来进行分析。由于ServiceManager不是从BnServiceManager中派生的,所以之前没有讲数据请求是如何从通信层传递到业务层并进行处理的。我们以MediaPlayerService和它的Client作为分析对象,试着解决这些遗留问题。
查询ServiceManager
一个Client想要得到某个Service的信息,就必须先和ServiceManager打交道,通过调用getService函数来获取对应Service的信息。getService函数的代码如下:
1 | /* |
有了BpMediaPlayerService,就能够使用任何IMediaPlayerService提供的业务逻辑函数了。
调用这些函数都能够把请求数据打包发送给Binder驱动,并由BpBinder的handle值找到对应端的处理者来处理,这中间的过程如下所示:
- 通信层接收到请求。
- 提交给业务层处理
MediaPlayerService细节
在上文中我们可以看到,MediaPlayerService驻留在MediaPlayer进程中,这个进程有两个线程在talkWithDriver,假设其中有一个线程收到了请求消息,它最终通过executeCommand调用来处理这个请求,实现代码如下所示:
1 | status_t IPCThreadState::executeCommand(int32_t cmd) |
BBinder与业务层的关系我们可以通过这张图来梳理一下:
BnMediaPlayerService实现了onTransact函数,它将根据消息码调用对应的业务逻辑函数,这些业务逻辑函数由MediaPlayerService来实现,这些过程写在了Binder.cpp和IMediaPlayerService.cpp中,如下所示:
1 | status_t BBinder::transact( |
1 | status_t BnMediaPlayerService::onTransact(uint32_t code,const Parcel & data,Parcel *reply,uint32_t flags) |
Binder的实现
Binder的驱动代码在kernel/drivers/staing/android/binder.c中,另外该目录下还有一个binder.h头文件。/proc/binder目录下的内容可用来查看Binder设备的运行状态。
Binder和线程的关系
以MS(MediaPlayerService)为例,如果现在程序运行正常,那么MS在进行两个动作:
通过startThreadPool启动一个线程,这个线程在talkWithDriver.
主线程通过joinThreadPool也在talkWithDriver.
如果在业务逻辑上需要与ServiceManager交互,比如说要调用listServices打印所有服务的名字,假设这是MS中的第三个线程,按照之前的分析,它最终会调用IPCThreadState的transact函数,这个函数会talkWithDriver并把请求发到ServiceManager进程,然后等待来自Binder设备的回复。现在有三个进程在talkWithDriver。
ServiceManager处理完了listServices,把回复结果写回Binder驱动,调用listServices的那个线程就会得到这个结果,一一对应。
Binder消息通知
在Binder系统中,如果对应的BnXXX被终止了,那么我们可以通过一些方式收到这个通知
注册对应的监听需要做以下两件事:
从IBinder::DeathRecipient派生一个类,并实现其中的通知函数binderDied。这个函数会在BnXXX被终止时调用。
把这个类注册到系统中,告诉它你关系哪一个BnXXX的生死
这个消息是这么被收到的呢?其实也在executeCommand中,通过Proxy(对应着已经死亡的BBinder)发送消息
如果注册监听的进程率先被终止了,那么可以通过调用unlinkToDeath取消对对应的BnXXX死亡的监听
匿名Service
匿名Service就是没有注册的Service,包含了以下两个意思:
- 没有注册Service意味着这个Service没有在ServiceManager上注册
- 它是一个Service又表示它确实是一个基于Binder通信的C/S结构
可以通过下面这个例子来了解:
1 | status_t BnMediaPlayerService::onTransact(uint32_t code, const Parcel & data,Parcel *reply,uint32_t flags) |
当MediaPlayerClient调用create函数时,MediaPlayerService会返回一个IMediaPlayer对象,此后,MediaPlayerClient即可直接使用这个IMediaPlayer来进行跨进程的函数调用了。
BpMediaPlayer实际上是通过这个方法来得到BnMediaPlayer的handle的值的。
1 | reply->writeStrongBinder(player->asBinder()); |
当这个reply写到Binder驱动中时,驱动可能会特殊处理这种IBinder类型的数据,例如为这个BBinder建立一个独一无二的handle,这其实相当于在Binder驱动中注册了一项服务。
通过这种方式,MS输出了大量的Service,例如IMediaPlayer和IMediaRecorder等。
Service的实现实例(Native层)
纯Native的Service表示代码都在Native层。Native层有有很多Service,前面的MediaPlayerService就是一个例子。
如果我们要新建实现一个Service,也完全可以模仿MS,代码示例如下:
1 | int main() |
Test是这么定义的呢?我们是跨进程的C/S,所以本地需要一个BnTest,对端需要提供一个代理BpTest。为了不暴露Bp的身份,Bp的定义和实现都放在BnTest.cpp中了。
ITest接口表明了它所能提供的服务,例如getTest和setTest等,这个与业务逻辑相关,代码如下所示:
1 | //需要从IInterface派生 |
为了把ITest融入到Binder系统,需要定义BnTest和对客户端透明的BpTest。BnTest定义既可以与上面的Test定义放在一块,也可以分开,如下所示:
1 | class BnTest:public BnInterface<ITest> |
另外,我们还要使用IMPLEMENT宏。参考BnMediaPlayerService的方法,把BnTest和BpTest的实现都放在ITest.cpp中,如下所示:
1 | IMPLEMENT_META_INTERFACE(Test,"android.Test.ITest");//IMPLEMENT宏 |
BpTest示例如下:
1 | class BpTest:public BpInterface<ITest> |
这样C/S的框架就写好了。
Service的实现实例(Java层)
在Java层中,如果想要利用Binder进行跨进程的通信,也得定义一个类似ITest的接口,不过,这是一个aidl文件,假设服务端程序都在com.test.service包中。
ITest.aidl文件内容如下:
1 | package com.test.service; |
编译之后,会在gen目录下生成一个com.test.ITest.java文件,它实现了类似BnTest的一个东西,具体的业务实现还需要从ITest.Stub派生,实现代码如下所示:
1 | /* |
此时根目录下有这两个目录:
src下有一个com.test.service包结构目录
gen下也有一个com.test.service包结构目录
实现代理端:代理端往往在另一个程序中使用,假设是com.test.client包,把刚才com.test.service工程中gen下的com.test.service目录全部复制到com.test.client中,这样,client工程也就有两个包结构目录了:
com.test.client
com.test.service。不过这个目录中仅有aidl生成的Java文件
服务端一般驻留于Service进程,所以可以在Client端的onServiceConnected函数中获得代理对象,实现代码如下所示:
1 | //不一定是在onServiceConnected中,但它是比较合适的。 |
AIDL支持简单数据结构与Java中String类型的数据进行跨进程传递,如果想做到跨进程传递复杂的数据结构,还须另做一些工作。
以ITest.aidl文件中使用的complicatedDataStructure为例:
它必须实现implementsParcelable接口
内部必须有一个静态的CREATOR类
定义一个complicatedDataStructure文件。
在实现了Java文件后,我们还需要实现aidl类,如下:
1 | package com.test.service; |
然后在使用它的aidl文件中添加这行代码即可:
1 | import com.test.complicatedDataStrucrure; |
Android提供AIDL语言以及AIDL解释器自动生成一个服务器的Bn端,即Bp端用于处理Binder通信的代码,当我们写好ITest.aidl文件之后,我们使用aidl工具将其解析为一个实现了Bn端及Bp端通过Binder进行通信的Java代码,当完成aidl解析之后,开发者需要继承XXX.Stub类并实现其抽象方法。
Java Binder整体架构
Java层的Binder其实也是一个C/S架构,而且在类的命名上尽量保持与Native层一致,因此也可认为,Java层的Binder架构是Native层Binder架构的一个镜像。Java Binder中的成员如下图所示:
其中:
Binder和BinderProxy类分别实现了IBinder接口。其中Binder类作为服务端的Bn的代表,而BinderProxy作为客户端的Bp的代表(由linkToDeath()函数也可见一斑,native层中Bp正是由这个函数来实现消息通知)
BinderInternal类是一个仅供Binder框架使用的类。它内部有一个GcWatcher类,该类专门用于处理和Binder相关的垃圾回收
IBinder接口类中定义了一个叫FLAG_ONEWAY的整型变量,在调用Binder函数时,在指明了FLAG_ONEWAY标志后,函数就变成了非阻塞式调用(类似于回调)
Java层Binder需要借助Native层Binder系统来开展工作,即镜像和Native有着千丝万缕的关系,一定要在Java层Binder正式工作之前建立这种关系,Java层Binder框架的初始化顺序如下:
Java初创初期,系统会提前注册一些JNI函数,其中有一个register_android_os_Binder函数来专门搭建Java Binder和Native交互关系,在register_android_os_Binder函数中,完成了JavaBinder架构中最重要的三个类的初始化工作,顺序如下:
Binder类的初始化:使用gBinderOffsets对象保存了和Binder类相关的某些在JNI层使用的信息,用来在JNI层对Java层的Binder对象进行操作
BinderInternal类的初始化:获取一些有用的methodID和fieldID,表明JNI层一定会向上调用Java层的函数,以及注册相关类中native函数的实现。
BinderProxy类的初始化:获取WeakReference类和ERROR类的一些信息,BinderProxy对象的生命周期会委托WeakReference来管理,所以JNI层会获取该类get函数的MethodID
框架的初始化其实就是提前获取一些JNI层的使用信息,如类成员的MethodID、类成员变量的fieldID等。它能节省每次使用时获取这些信息的时间。当Binder调用频繁时,这些时间的积累也不容小觑。同时,这个过程中所创建的几个全局静态对象为JNI层访问Java层的对象提供了依据。每个初始化函数中所执行的registerNativeMethods()方法则为Java层访问JNI层打通了道路。换句话说,Binder初始化的工作就是通过JNI建立起Native Binder与Java Binder之间互相通信的桥梁。
ActivityManagerService
与native中MediaServer的例子一样,这里我们也使用一个具体的例子来解释Java层Binder的工作原理
AMS如何将自己注册到ServiceManager
AMS通过addService函数将一个叫做JavaBBinder的对象添加到Parcel中,而最终传递到Binder驱动的正是这个JavaBBinder对象。Java层中所有的Binder对应的都是这个JavaBBinder对象,不同的Binder对象对应不同的JavaBBinder对象。
JavaBBinder是从BBinder(native)派生的
ActivityManagerService相应请求
JavaBBinder仅仅是一个传声筒,它本身不实现任何业务函数,其工作是:
当它收到请求时,只是简单地调用它所绑定的Java层的Binder对象的exeTransact
该Binder对象的exeTransact调用其子类实现的onTransact函数
子类的onTransact函数将业务又派给其子类来完成
通过这种方式,来自客户端的请求就能传递到正确的Java Binder对象了,下图展示了AMS相应请求的整个流程。
总结
在本章的学习中,我们学习了native中的Binder和Java中的Binder,总结分别如下:
native层Binder
Binder体系的总体架构,Client、Server和ServiceManager三者之间的关系(包括交互的先后关系)、
MediaPlayerService注册服务解析,通过handle=0找到ServiceManager注册,最后MediaPlayerService有两个线程处理请求
ServiceManager解析,ServiceManager成为manager需要将自己的handle置为0,它的作用是对需要注册的服务进行管理,包括权限控制等,同时帮助Client找到对应的Server
Client通过字符串(Service的名称)来得到对应的服务,这里以MediaPlayerService为例,展示了Client与MediaPlayerService交互的细节,哪个线程发起请求,答复就会送回到哪个请求
Binder对于匿名Service的管理,匿名Service可以通过在Binder驱动中的操作来注册自己(其实就是给自己一个独一无二的handle),以及对死亡进程的监听,native层与Java层Service的实现
native层Binder
Java层的Binder可以用一张图来总结,因为Java层的Binder十分依赖native层的Binder。