理解Binder框架
Binder是Android系统进程间通信(IPC)最重要的方式。要想了解Android的系统原理,必须要先对Binder框架有一定的理解。Binder是什么?Binder可以理解为能在进程间进行”通信”的对象,这个通信不是指在不同进程中操作同一个对象,而应理解为一种通信协议。
Binder的引入背景
传统的进程间通信方式有管道,消息队列,共享内存等,其中管道,消息队列采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。共享内存虽然无需拷贝,但控制复杂,难以使用。socket作为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信。Binder通过内存映射的方式,使数据只需要在内存进行一次读写过程。
内存映射,简而言之就是将用户空间的一段内存区域映射到内核空间,映射成功后,用户对这段内存区域的修改可以直接反映到内核空间,相反,内核空间对这段区域的修改也直接反映用户空间。那么对于内核空间<—->用户空间两者之间需要大量数据传输等操作的话效率是非常高的。
Binder的通信模型
主要分为4个部分Binder驱动,Client,Server,ServiceManager(SM),其中Client,Server,ServiceManager(SM)指的是用户空间,并且分别在不同的进程。Binder驱动是内核空间,Binder驱动是一段c语言实现的代码。
服务分系统服务和本地服务,系统服务一般指设备开机时就创建供所有应用使用的服务,如AMS,PMS等,本地服务一般指本地创建的Service,一般供当前应用使用。
由上图可以看出Binder通信的流程是这样的:
- 首先不同的服务Server一般为service需要先在SM上进行注册
- Client想获取服务时,先到SM上通过服务名请求服务
- 得到对应的服务的引用,通过这个引用进行进一步的通信。
由于Client、Server、SM不在同一个进程,所以都需要借助Binder驱动完成通信。这其中有几个关键点。先明确几个概念,Binder Server对象是指真正的进行服务的对象,Biner内核对象是根据Binder Server对象在内核空间的一种表述方式,因为语言层面不同,所以具体的表现形式也不同。比如用应用层表现为Java类或C++类,在内核层表现为结构体了。也可以称为实体,主要是和引用区分开,实体只有一个,而引用可以有很多。
- Client、Server、SM不在同一个进程,最初Client和Server怎么得到SM的通信的?
- 具体的注册流程是怎样的?
- Binder内核对象,BinderProxy对象是同一个对象吗,有什么联系?
首先第一个问题,首先ServiceManage进程本身就是也是采用Binder通信,在系统初始化的时候使用BINDER_SET_CONTEXT_MGR命令将自己注册为ServiceManage,这个过程会在内核空间产生一个ServiceManage的Binder实体。而这个Binder就在内核的0号引用,其他进程通过0号引用找到ServiceManage的Binder实体,通过内存映射就可以和SM建立联系。
第二个问题,首先拥有服务名的Server进程向SM注册时需要借助Binder驱动,此时创建了一个在内核的实体对象,和Server的对象是不同的结构,但拥有Server实体对象的关键信息,比如可以提供的方法等。这个实体对象有一项数据保存Server实体的引用(在内存中可以理解为映射的地址),借助Binder驱动将内核中的实体对像的引用和服务名注册到SM中,这样就有这样一条关系链,SM中服务名—>内核的实体对象的引用—>Server实体对象。
第三个问题,Binder内核对象,BinderProxy对象是同一个对象可定不是同一个对象,因为对应不同的空间,语言层面都有不一样,BinderProxy是系统根据Binder内核对象在Cilent进程新建(new)的一个Binder对象,里面包含Binder内核对象的关键信息,比如所能提供的方法等,这个对象就和真正远程的Server实体对象很相似了,这样Client对象就能像操作Server实体对象一样进行操作,不用考虑Server实在远程还是本地。但具体方法的实现还要通过代理的方式调用远程对象来实现。一般通过transact()和onTransact()来实现代理的调用。
常见的Binder通信过程分析
以startActivity为例。
上图是startActivity过程中的一部分,这里从ContextImpl.startActivity开始分析。
Step1 获得ActivityManagerService在Client的代理
在Instrumenttation中的execStartActivity获得ActivityManagerProxy,过程如下:
|
|
ActivityManagerNative.java
|
|
首先通过ServiceManager.getService(“activity”)获取已经注册到
ServiceManager的AMS服务,返回一个IBinder对象,其实这个对象是个BindeProxy对象,BindeProxy在Binder类中可以找到,BinderProxy就是AMS对象在Client中的代理,通过底层C++生成。接着对这个BinderProxy在做一层代理,生成ActivityManagerProxy对象,为什么还要在继续一次代理呢?主要是以因为BindeProxy是一个基本的Binder对象,其并没有实现对数据封装的以及其他的具体逻辑的处理,以及IActivityManager接口的实现。
Step2 Client与远程(系统进程)ActivityManagerService通信——在Client进程
ActivityManagerProxy.java
|
|
ActivityManagerProxy实现IActivityManager的接口,这里的mRemote就是上面的BinderProxy对象,通过transact方法进行请求,接下里就是IPC过程到AMS系统进程了。
Step3 系统进程里的ActivityManagerService进行请求处理
ActivityManagerNative.java
ActivityManagerNative是一个抽象类,其实现类就是AMS,通过Binder驱动等一系列IPC过程,上面的
|
|
最终会调到ActivityManagerNative的onTransact中,ActivityManagerNative通过code值START_ACTIVITY_TRANSACTION来判断具体是哪个请求,因为在不同的进程中方法是没法传递的,所以需要定义一些协议来进行通信,这些协议也就是不同方法对应的code值。
|
|
ActivityManagerNative是个抽象类,startActivity在在其子类AMS中实现,之后的流程在Android 应用点击图标到Activity界面显示的过程分析。
Binder的使用
只有允许不同应用的客户端用 IPC 方式访问服务,并且想要在服务中处理多线程时,才有必要使用 AIDL。 如果您不需要执行跨越不同应用的并发 IPC,就应该通过实现一个 Binder 创建接口;或者,如果您想执行 IPC,但根本不需要处理多线程,则使用 Messenger 类来实现接口。无论如何,在实现 AIDL 之前,请您务必理解绑定服务。官方文档有详细的讲解
1.利用Binder不使用AIDL实现IPC的过程
服务端:
|
|
注册服务,并指定为远程进程:
|
|
如果被设置的进程名是以一个冒号”:”开头的,则这个新的进程对于这个应用来说是私有的,当它被需要或者这个服务需要在新进程中运行的时候,这个新进程将会被创建。如果这个进程的名字是以小写字符开头的,则这个服务将运行在一个以这个名字命名的全局的进程中,当然前提是它有相应的权限。这将允许在不同应用中的各种组件可以共享一个进程,从而减少资源的占用。remote这个名称是可以随意起的,关键的是冒号”:”。
客户端:
|
|
绑定服务:
|
|
具体的过程是NoAidlService需要先在Manifest注册信息,这相当于是将服务信息注册到ServiceManage。客户端用Intent将NoAidlService的服务名等信息包装,通过bindService在ServiceManage里寻找NoAidlService,如果成功,则通过onServiceConnected回调得到服务端的信息ComponentName,和服务通信接口clientBinder。
这个clientBinder和NoAidlService通过onBind返回的mNoAidlBinder相似,如果是远程服务,就不是同一个对象,可以理解为是通过Binder驱动得到的一个代理ProxyBinder对象,但看起来和mNoAidlBinder是同一个对象,其实不是的。但如果在一个进程,两个就是同一个对象。
clientBinder通过transact指定服务端对应的函数code比如TRANSACTION_studyBinder,来调用服务端的相应函数,通过Binder驱动,最后会调用到服务端NoAidlBinder的onTransact,通过判断code来确定相应的函数,然后通过再将结果返回,这个过程对Client是阻塞性的,所以客户端调用最好是在异步线程和服务端通信。
对服务端,系统会为每个服务提供线程池,这也容易想到,因为不可能让不同的服务为在调用服务时相互等待。
2.使用AIDL来和Service通信AIDL
AIDL实现IPC通信Demo
AIDL即Android Interface Definition Language,Android接口定义语言。它是一种IDL语言,可以拿来生成用于IPC的代码。
其实就是AIDL文件生成一个帮助类,屏蔽parcel的读写细节,让客户端使用者专注于业务的实现。有一点需要注意的是,如果Service不是在一个新的进程,通过IBinder.queryLocalInterface(DESCRIPTOR)的方式得到本地的Binder对象,也就是和onBind返回的对讲是同一个,就不会涉及到跨进程通信和Binder驱动的调用。这点和不使用AIDL实现IPC是一样的。