欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android Binder机制

程序员文章站 2022-04-13 11:31:03
...

1、binder是什么

Binder的英文原意是“胶水”的意思,其实很形像了。Binder模糊了进程边界,淡化了进程间通信的过程,整个系统仿佛运行于同一个面向对象的程序之中。形形色色的Binder对象以及星罗棋布的引用仿佛粘接各个应用程序的胶水。

要理解binder当然要从Linux说起。

2、Linux基础

为了保护进程空间不被别的进程破坏或者干扰,Linux中的进程是相互独立的,也就是所谓的进程隔离。(而且一个进程的内存空间还被分为了用户空间和内核空间,二者也是相互隔离的。这里不做探讨)所以在Linux中,进程与进程之间是相互隔离的,而且进程中的用户和内核空间也是隔离的。

用户空间访问内核空间的唯一方式就是系统调用;通过这个统一入口接口,所有的资源访问都是在内核的控制下执行,以免导致对用户程序对系统资源的越权访问,从而保障了系统的安全和稳定。

也就是说为了安全和独立,一个进程是不能直接操作或者访问另外一个进程的内存空间的。他们之间既然是隔离的,在需要通信、协作的时候就需要使用进程间通信技术(即IPC,也称跨进程通信),我们都知道Android框架是建立在Linux之上的,当然也会面对进程间通讯的问题。

3、为什么要用binder呢?

在传统的Linux上,我们还是有很多选择可以用来实现进程间通信,如管道、SystemV、Socket等。那么Android为什么不使用这些原有的技术,而是要使开发一种新的叫Binder的进程间通信机制呢?

主要有两个方面的原因:

  1. 性能方面
    在移动设备上(性能受限制的设备,比如要省电),广泛地使用跨进程通信对通信机制的性能有严格的要求,Binder相对出传统的Socket方式,更加高效。Binder数据拷贝只需要一次,而管道、消息队列、Socket都需要2次,共享内存方式一次内存拷贝都不需要,但实现方式又比较复杂。

  2. 安全方面
    传统的进程通信方式对于通信双方的身份并没有做出严格的验证,比如Socket通信ip地址是客户端手动填入,很容易进行伪造,而Binder机制从协议本身就支持对通信双方做身份校检,因而大大提升了安全性。

还有一些好处,如实现面象对象的调用方式,在使用Binder时就和调用一个本地实例一样。

binder运行机制

Binder基于Client-Server通信模式,除了Client端和Server端,还有两角色一起合作完成进程间通信功能。

Binder通信的四个角色:

  • Client进程:使用服务的进程。
  • Server进程:提供服务的进程。
  • ServiceManager进程:ServiceManager的作用是将字符形式的Binder名字转化成Client中对该Binder的引用,使得Client能够通过Binder名字获得对Server中Binder实体的引用。
  • Binder驱动:驱动负责进程之间Binder通信的建立,Binder在进程之间的传递,Binder引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。

初次接触这些概念可能会觉得难于理解,读者可以把四个角色和熟悉的互联网进行类比:Server是服务器,Client是客户终端,ServiceManager是域名服务器(DNS),驱动是路由器。这样类比,你很容易就能理解下图:

Android Binder机制

Binder的运行机制就很好理解了,Server进程向Service Manager进程注册服务(可访问的方法接口),Client进程通过Binder驱动可以访问到Server进程提供的服务。Binder驱动管理着Binder之间的数据传递,这个数据的具体格式由Binder协议定义(可以类比为网络传输的TCP协议)。并且Binder驱动持有每个Server在内核中的Binder实体,并给Client进程提供Binder的引用。

Binder跨进程传输并不是真的把一个对象传输到了另外一个进程;传输过程好像是Binder跨进程穿越的时候,它在一个进程留下了一个真身,在另外一个进程幻化出一个影子(这个影子可以很多个);Client进程的操作其实是对于影子的操作,影子利用Binder驱动最终让真身完成操作。

每个Android的进程,只能运行在自己进程所拥有的虚拟地址空间。对应一个4GB的虚拟地址空间,其中3GB是用户空间,1GB是内核空间,当然内核空间的大小是可以通过参数配置调整的。对于用户空间,不同进程之间彼此是不能共享的,而内核空间却是可共享的。Client进程向Server进程通信,恰恰是利用进程间可共享的内核内存空间来完成底层通信工作的,Client端与Server端进程往往采用ioctl等方法跟内核空间的驱动进行交互。

举例分析

首先我们看看我们的程序跨进程调用系统服务的简单示例,实现浮动窗口部分代码:

//获取WindowManager服务引用
WindowManager wm = (WindowManager)getSystemService(getApplication().WINDOW_SERVICE);  
//布局参数layoutParams相关设置略...
View view=LayoutInflater.from(getApplication()).inflate(R.layout.float_layout, null);  
//添加view
wm.addView(view, layoutParams);

系统服务都是运行在systemServer进程中,因此我们调用系统服务都是跨进程的调用。第2行代码中,得到的wm是WindowManager对象的引用,第6行调用WindowManager的addView函数,将触发远程调用,调用的是运行在systemServer进程中的WindowManager的addView函数。那么addView函数做了什么呢?我们继续看。

代码执行过程

假设你已经创建好服务端类MyService、客户端类MyClient。在客户端持有MyService的引用,并且调用了MyService的func函数,那么Android内部调用过程如下:

Android Binder机制

代码调用过程

看了这个图以后,相信你对你的代码在调用远程进程函数时有个全局的认识。这张图有一点很重要,就是客户端当前线程会被挂起!因此,如果远程进程是执行长时间的运算,请不要使用主线程去调用远程函数,以防止ANR。

Binder的C/S架构

上面一节我们对远程进程调用代码执行过程有个初步了解,在Android开发中,我们大量使用到了系统Service,比如媒体播放、各种传感器以及WindowManagerService等等等等(太多了~)。那么Android是怎么管理这些服务,并且让用户跨进程调用这些服务呢?首先我们看看调用系统服务的过程。在Android开机启动过程中,Android会初始化系统的各种Service,并将这些Service向ServiceManager注册(即让ServiceManager管理)。客户端想要得到具体的Service直接向ServiceManager要即可。客户端首先向ServiceManager查询得到具体的Service引用,然后通过这个引用向具体的服务端发送请求,服务端执行完成后就返回。

Android Binder机制

Binder驱动实现原理

服务端跨进程的类都要继承Binder类。我们所持有的Binder引用(即服务端的类引用)并不是实际真实的远程Binder对象,我们的引用在Binder驱动里还要做一次映射。也就是说,设备驱动根据我们的引用对象找到对应的远程进程。客户端要调用远程对象函数时,只需把数据写入到Parcel,在调用所持有的Binder引用的transact()函数,transact函数执行过程中会把参数、标识符(标记远程对象及其函数)等数据放入到Client的共享内存,Binder驱动从Client的共享内存中读取数据,根据这些数据找到对应的远程进程的共享内存,把数据拷贝到远程进程的共享内存中,并通知远程进程执行onTransact()函数,这个函数也是属于Binder类。远程进程Binder对象执行完成后,将得到的写入自己的共享内存中,Binder驱动再将远程进程的共享内存数据拷贝到客户端的共享内存,并唤醒客户端线程。

Binder机制运用

好了,现在对Binder机制已经理解了,我们再看看Android是怎么运用Binder的。再现前面代码:

//获取WindowManager服务引用
WindowManager wm = (WindowManager)getSystemService(getApplication().WINDOW_SERVICE);  
//布局参数layoutParams相关设置略...
View view=LayoutInflater.from(getApplication()).inflate(R.layout.float_layout, null);  
//添加view
wm.addView(view, layoutParams);

这段代码前面已经出现过。getSystemService(getApplication().WINDOW_SERVICE);函数内部原理就是向ServiceManager查询标识符为getApplication().WINDOW_SERVICE的远程对象的引用。即WindowManager对象的引用,这个引用的真正实现是WindowManager的某个代理。得到这个引用后,在调用addView时,真正的实现是在代理里面,代理把参数打包到Parcel对象中,然后调用transact函数(该函数继承自Binder),再触发Binder驱动的一系列调用过程。

参考链接:https://www.jianshu.com/p/04a034cbbc27


相关标签: Binder