Android面试一天一题(Day 35:神秘的Binder机制)

有些东西你一直在用它,但却一直不知道自己在使用它。在Android的应用开发中,Binder就是这么一个角色,你编写的应用都使用过Binder机制(如startActivity的执行过程),但是你却说不出什么是Binder,甚至很多人都不知道Binder的存在。

那么,这种已经融入你的使用但又不让人发觉的机制对我们做应用开发有用吗?很多人认为并没有用,往往在他们的印象里Binder是和驱动一起出现的,叫“Binder驱动”,而我们做应用层开发的基本上不需要去修改或者开发驱动。这样一来,Binder对于应用开发者来说就显得很神秘但是和自己无关。

面试题:Binder是什么?它是如何实现跨进程通信的?

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

要理解Binder当然要先从Linux进程说起。

Linux进程基础

为了保护进程空间不被别的进程破坏或者干扰,Linux的进程是相互独立的(进程隔离),而且一个进程空间还分为用户空间和内核(Kernel)空间,相当于把Kernel和上层的应用程序抽像的隔离开。这里有两个隔离,一个进程间是相互隔离的,二是进程内有用户和内核的隔离。

即然有隔离,那么它们之前要相互配合时就得有合作(交互)。进程间的交互就叫进程间通信(IPC,或称跨进程通信),而进程内的用户和内核的交互就是系统调用。

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

也就是说,为了保证安全性和独立性,一个进程是不能直接操作或者访问别一个进程空间的。Android即然是架设在Linux基础之上的,当然也要解决这个进程间通信的问题。

为什么要使用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),驱动是路由器。这样类比,你很容易就能理解下图:

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

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

Binder的线程管理

每个Binder的Server进程会创建很多线程来处理Binder请求,可以简单的理解为创建了一个Binder的线程池吧(虽然实际上并不完全是这样简单的线程管理方式),而真正管理这些线程并不是由这个Server端来管理的,而是由Binder驱动进行管理的。

一个进程的Binder线程数默认最大是16,超过的请求会被阻塞等待空闲的Binder线程。理解这一点的话,你做进程间通信时处理并发问题就会有一个底,比如使用ContentProvider时(又一个使用Binder机制的组件),你就很清楚它的CRUD(创建、检索、更新和删除)方法只能同时有16个线程在跑。

Binder对应用开发者的用处

上面提到的线程数是一个对应用开发有用的地方,很多人之前由于不了解有这个线程数的限制可能会在这里遇到过麻烦。

当然,最重要的是你了解了Android的进程间通信机制,知道Android的组件间是如何实现进程间通信和数据共享的,当你的应用要处理进程间通信时,你知道可能要关注哪个方面。如你的Activity组件可以设置在不同的进程中运行,那么每个进程都是独立的,它要使用别的进程的数据时,你就会知道用静态变量去交互没有意义。而且,理解了原理,再去编写AIDL的代码就没有那么难理解了。

对于一些Framework层的开发者,如自已定制ROM的团队,需要提供自己的系统服务时,Binder机制肯定是必需要了解的,不然无从下手。

小结

多进程问题不是每个开发都会遇到的,很多应用只要在自己的进程内完成业务需求就可以了,但知道多进程的存在,并理解Android提供的Binder机制,那么我们在选择如何做进程间的互交时就会更明确可能存在的问题和Android的解决方案。

也许,这几段讲解后,你还是一知半解的,那么请看下一篇和AIDL相关的实战。

推荐阅读更多精彩内容