Binder机制可谓是Android 知识体系的重中之中,作为偏底层的基础组件,平时我们很少关注它,但是它却无处不在,这也是android面试考察点之一,本篇将从流程上将Binder通信过一遍。
文章目录
1:Binder作用
2:进程与Binder驱动如何通信
3:ServiceManager进程的作用
4:进程添加服务到ServiceManager 的流程
5:进程从ServiceManager获取服务的流程
6:Binder服务端数据接收
7:Binder通信全流程图
先看下Linux下进程地址映射关系:
我们知道:对象调用本身就是地址空间的访问。
如上:进程之间各自访问的各自内存地址,他们之间无法直接访问对方的地址,也就是说微信是不能直接调用支付宝提供的接口,但是内核具有访问其他进程地址空间的权限,因此微信可以将消息发送给内存,让内核帮忙转发给支付宝,这种方式就叫:存储/转发 方式。
基于这种方式衍生了几种 IPC(进程间通信);
1:管道,消息队列,socket等。
而Android采用了新的机制:Binder ,这种方式只需要一次数据拷贝,并且Binder更加安全。
既然需要内核进行消息中转,那么BInder驱动需要运行在内核空间,而事实上也的确如此,Binder驱动加载后再内核区间运行,进程只需要和Binder驱动取得联系,通过Binder驱动联系另一个进程,那么一次消息的传送过程就可以实现了。
内核提供一系列的系统调用接口给用户进程使用,当用户进程 想要访问内核时,只需要调用对应的接口,此时代码就会从用户空间切换到内核空间。
那么常见的系统调用函数接口如:
open/ read/ wirte/ioctl/ close/ mmap/ fork 等。
用户进程与BInder通信步骤
1:打开Binder驱动: open("/dev/binder", O_RDWR| O_CLOEXEC);
2: 通过ioctl 与Binder驱动进行数据通信: ioctl(mDriverFD, BINDER_WRITE_READ,&bwr);
其中参数 bwr是读写数据结构。
先看下:Binder Client、Binder Server、ServiceManager之间关系
为了方便:ServiceManager简称为 SM。
Binder设计为C/S架构,C为Clinet(客户端),S为Server(服务端),Server端提供接口给Client使用,而这个服务是以Binder引用的形式提供的。
我们知道,C和S是不同的进程,那么C如何拿到S的Binder引用了?
你可能会说,当然是SM了,S先将Binder引用存放在SM里,当C需要的时候向SM查询即可。
这样似乎可以讲通,但是SM也是一个单独的进程,那么S,C如何与SM通信了?这个实际上就陷入了 先有鸡还是先有蛋的问题。
其实面对这样的问题,一般就是系统给初始化。什么意思?
系统先将 SM作为特殊的 Binder(handle = 0)提前放入Binder驱动,当C,S想要获取SM的Binder引用,只需要获取 handle = 0的Binder即可。
SM注册进Binder驱动后就会 等待Binder驱动的消息,这里列举两个常见的处理消息的场景
1:其他进程添加服务到 SM这里。
2:其他进程向SM插叙服务。
这里SM维护着一个链表,链表的元素是结构体
它主要记录 name和 handle字段。
1:当SM收到添加服务的指令后,从Binder驱动里取出 handle和name ,并构造结构体插入到 链表。
2:当SM收到查询服务的指令后,从 Binder驱动里取出 name, 并找到链表里对应name的 handle, 然后写入 Binder驱动,Binder驱动最后转发给 查询进程。
我们以振动服务作为切入点来分析
tranceBeginAndSlog("StartVibratorService")
vibrator = new VibratoeService(context);
ServiceManager.addService("vibrator", vibrator);
tranceEnd();
在system_server 进程里构造振动服务(VibratorService继承自Binder),并添加到SM里
system_server / addService
1:先找到 ServiceManager
2: 往ServiceManager里添加服务
getIserviceManager()
其中 BInderINternal.getContextObject()是 native方法。我们重点看下 Native层。
serviceManager对应的 BpBinder对象,没有找到的话,创建新的并存取缓存里,方便下次直接获取。
1:ProcessState里维护了一个单例,每个进程只有一个ProcessState对象,创建ProcessState时候就会打开Binder驱动,同时会设置Binder线程池里线程个数等其他参数。
2:Native层构造BpBinder(handle = 0) 表示该BpBinder是ServiceManager在客户端的引用,同时在构造 BinderProxyNativeData持有BpBinder。
3: 构造BinderProxy对象并持有BinderProxyNativeData, 也就是间接持有BpBinder
4: 最后构造ServiceManagerProxy对象,它实现了 IServiceManager接口,它的成员变量mRemote指向了 BinderProxy。
可以看出,获取ServiceManager的过程并不是真正去获取ServiceManager的Binder对象,而是获取它再当前进程的代理: BpBinder
既然刚刚我们找到了SM的Binder代理,接下来看看我们应该如何使用它给 SM添加服务。
#ServiceManagerNative.ServiceManagerProxy
public void addService(string name, IBinder service,boolean allowIsolated,int dumpPriority)
{
// 构造Parcel
Parcel data = Parcle.obtain();
Parcel reply = Parcle.obtain();
data.writeInterfaceToken(IServiceManager.descriptor);
data.writeString(name);
// 写入Binder
data.writeStrongBinder(service);
data.writeInt(allowIsolated ? 1:0);
data.writeInt(dumpPriority);
// 通过BinderProxy发送
mRemote.transact(ADD_SERVICE_TRANSACTION, data,reply,0);
reply.recycle();
data.recycle();
}
其中 IPCThreadState与线程相关,不同的线程会维护一个单例。
由此可见,最终还是通过BpBinder 发送消息,进而发送到 Binder驱动上。
此时Binder驱动收到的消息包括但不限于:
1:服务的名字
2:ServiceManager的handle
3: BBinder对象指针
驱动建立服务handle和BBinder对象指针的映射关系,并将服务的名字和服务的 handle传递给SerivceManager(通过ServiceManager 的handle 查找)。
ServiceManger拿到消息后建立映射关系,等待其他进程的请求。
至此,进程添加服务到 ServiceManager过程已经分析完毕,可以用图表示如下:
BBinder作用
java层传递的是Binder对象,那么如何与 Native的 BBinder关联起来了?
重点就在:下面这行代码。
Parcel.writeStrongBinder(Binder)
也就是说,Server端的 java Binder对象在 Native层的代表是BBinder。
Binder驱动记录了BBinder的地址,当有消息过来时通过找到BBinder对象进而找到java层的 Binder对象,最终调用 Binder.onTransact()。
振动服务添加完成后,某些进程想要获取振动服务,比如微信收到消息后需要振动用以提示用户。
我们看看,应该如何获取振动服务。
private void vibrate(){
// 获取振动服务
Vibrator vib = (Vibraator) getSystemService(Context.VIBRATOR_SERVICE);
// 开始振动
vib.virate(1000);
}
与添加服务类似,我们首先找到 SM,找到SM过程 和4.1是一样的,这里不必赘述。
#ServiceManagerNative.ServiceManagerProxy
public IBinder getService(string name) throws RemoteException
{
// 构造 Parcel
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IServiceManager.descriptor);
// 写入名字
data.writeString(name);
// 通过BinderProxy发送
mRemote.transact(GET_SERVICE_TRANSACTION,data, reply,0);
IBinder binder = reply.readStrongBinder();
reply.recycle();
data.recycle();
return binder;
}
由此可见,嘴周还是通过BpBinder发送消息,进而发送到 Binder驱动。
此时驱动收到的信息包括但不限于:
1:服务的名字
2: ServiceManager的 handle
Binder驱动收到消息后,找到SM,并将服务的名字传给SM,SM从自己维护的链表找到服务名相同的节点,并取出该服务的 handle, 最后发给Binder驱动,Binder驱动将handle转发给调用者
对比添加服务和获取服务的流程,两者前半部分都是相似的,都是先拿到SM的BpBinder引用,然后写入驱动,最后由SM进程处理,只是对于获取服务来说,还需要将查询的结果handle写入驱动返回给调用方。
问: 我们知道SM维护的链表中 handle是整形的,但是调用者接收的是Binder对象,这两个是怎么结合起来的了?
答: handle表示的是Binder服务端在客户端的索引句柄,只要客户端拿到了 handle,它就能通过Binder驱动调用到服务端。
(SM将查询到结果后将 handle写入驱动,然后从驱动将结果存入 reply字段,最后通过 reply拿到 Binder引用,重点方法是: reply.readStrongBinder())。
通过驱动返回的handle构造 BpBinder ,最终封装为 java层的 BinderProxy。
至此,获取服务的流程就结束了,用图简化就是
在微信进程拿到振动服务(在system_server进程里)的Binder(BinderProxy)后,就可以调用振动方法了,然后指令发送给驱动,驱动通过振动服务的handle找到对应的服务 BBinder指针,从而调用服务的接收方法。
下面来看下system_server进程是如何接收并处理指令的。
system_server进程启动的时候,就会开启Binder线程池,并等待驱动数据到来。
当sytem_server进程添加振动服务到SM时,会将 java层的 Binder转为 Native层的BBinder,并将 BBinder对象指针写入Binder驱动。
当微信进程调用 system_server接口时:
1:微信进程调用BpBinder.transact() 将handle和数据写入Binder驱动
2: Binder驱动根据handle找到 system_server进程
3:system_server进程从驱动拿到数据,并取出BBinder指针,最终调用 到 system_server进程java层的 Binder.onTransact();
如此一来:微信程序成功调用了振动服务,也就是所一次 Client和 Service端的通信就完成了。
整个设计中,核心是 handle,现在我们简单的总结一下
1:通过handle构造 Client端的BpBinder(Native层),与此对应的是java层的 BinderProxy
2:通过handle驱动找到server端进程,进而调用BBinder(Native层),与此对应的是java层的Binder。
3:通过handle的一系列中转,Client,transact() 成功调用了 Server。onTransact() 一次Binder通信过程就完成了。
以上:就是整个Binder机制的梳理过程,此间省略了Binder驱动里的映射逻辑,可以将BInder驱动当做一个黑盒,更重要的是 Binder客户端和服务端是如何进行映射的。