我的一个朋友在某互联网大厂(Alibaba)的Android开发岗工作了五年,最近几次奉命去当面试官,面了不少应聘者,这是他吐槽的发生在面试中的一些事情。
背景
我们搞技术的也没什么别的人脉圈子,平时也就哥几个吃个饭,喝个酒,吹个牛逼。
前阵子几个朋友小聚,大家喝了点酒就开始吹牛逼,他一边感叹了一下现在越来越少的休息时间一边说老板为什么还不给他转岗的机会,说着说着就开始感慨新来的那些员工。
“现在的这些应聘者,学历工作经验看着还亮眼,结果一问三不知。”他一脸吃到了苍蝇的表情“上次我们公司来了个211硕士学历的,简历上写着熟知底层,也做了不少项目,结果我问了个Binder 层的问题都讲不明白,真的是浪费时间,还不如回家先学好了再来……”
我想了下自己最近面试的那些新人,简历上一看,个个都是人才精英,学历有了很大的提升,一拿出来,什么985,211的不少,要么就是做过很多大项目的大佬,但是底层原理很多人也讲不出个所以然。
今天在这里就专门开文写一下这个知识点,希望在即将到来的“金三银四”面试季中对大家有所帮助。
前言
理解Binder对于理解整个Android系统有着非常重要的作用,Android系统的四大组件,AMS,PMS等系统服务无一不与Binder挂钩;如果对Binder不甚了解,那么就很难了解这些系统机制,从而仅仅浮游与表面,不懂Binder你都不好意思说自己会Android开发;要深入Android,Binder是必须迈出的一步。
在Android开发过程中,Binder的身影无处无处不在,我们编写的程序都使用过Binder机制(例如startActivity的执行过程)但是请问你知道什么是Binder么?在开发过程中你察觉到它的存在了么?Biner翻译过来是“胶水“的意思,这个翻译分形象。Binder的主要工作就是淡化了进程边界,淡化了进程间通信的过程。要是想更好的理解Binder就必须从Linux进程谈起。
Linux进程
为了保护进程空间不被别的进程破坏或者干扰,Linux中的进程是相互独立的,也就是所谓的进程隔离。(而且一个进程的内存空间还被分为了用户空间和内核空间,二者也是相互隔离的。这里不做探讨)所以在Linux中,进程与进程之间是相互隔离的,而且进程中的用户和内核空间也是隔离的。
也就是说为了安全和独立,一个进程是不能直接操作或者访问另外一个进程的内存空间的。他们之间既然是隔离的,在需要通信、协作的时候就需要使用进程间通信技术(即IPC,也称跨进程通信),我们都知道Android框架是建立在Linux之上的,当然也会面对进程间通讯的问题。
为什么使用Binder
在Linux系统中为了达到进程间通讯的目的,我们可以选用诸如管道、Socket等技术手段,那么为什么Android选用了Binder这种新型的IPC技术,放弃了原有成熟的技术呢?
主要有如下两个方面:
1.性能角度
由于在移动设备诸如省电等性能的考虑,广泛地使用进程间通讯对于通信机制的性能有严格的要求,Binder相对于传统的Socket、管道方式更加高效。Bidner数据拷贝只需要一次,而管道、消息队列、Socket都需要2次,内存共享方式一次内存拷贝都不需要,但是实现起来难度高,复杂性大。
2.稳定性角度
传统的进程通信方式对于通信双方的身份并没有做出严格的验证,比如Socket通信ip地址是客户端手动填入,很容易进行伪造,而Binder机制从协议本身就支持对通信双方做身份校检,因而大大提升了安全性。
Binder机制
一、Binder通信概述
binder通信是一种client-server的通信结构。
1.从表面上来看,是client通过获得一个server的代理接口,对server进行直接调用;
2.实际上,代理接口中定义的方法与server中定义的方法是一一对应的;
3.client调用某个代理接口中的方法时,代理接口的方法会将client传递的参数打包成为Parcel对象;
4.代理接口将该Parcel发送给内核中的binder driver.
5.server会读取binder driver中的请求数据,如果是发送给自己的,解包Parcel对象,处理并将结果返回;
6.整个的调用过程是一个同步过程,在server处理的时候,client会block住。
二、service manager
Service Manager是一个linux级的进程,顾名思义,就是service的管理器。这里的service是什么概念呢?这里的service的概念和init过程中init.rc中的service是不同,init.rc中的service是都是linux进程,但是这里的service它并不一定是一个进程,也就是说可能一个或多个service属于同一个linux进程。在这篇文章中不加特殊说明均指android native端的service。
任何service在被使用之前,均要向SM(Service Manager)注册,同时客户端需要访问某个service时,应该首先向SM查询是否存在该服务。如果SM存在这个service,那么会将该service的handle返回给client,handle是每个service的唯一标识符。
SM的入口函数在service_manager.c中,下面是SM的代码部分:
int main(int argc, char **argv){struct binder_state *bs;void *svcmgr = BINDER_SERVICE_MANAGER;bs = binder_open(128*1024);if (binder_become_context_manager(bs)) {LOGE(“cannot become context manager (%s)/n”, strerror(errno));return -1;}svcmgr_handle = svcmgr;binder_loop(bs, svcmgr_handler);return 0;}
这个进程的主要工作如下:
1.初始化binder,打开/dev/binder设备;在内存中为binder映射128K字节空间;
2.指定SM对应的代理binder的handle为0,当client尝试与SM通信时,需要创建一个handle为0的代理binder,这里的代理binder其实就是第一节中描述的那个代理接口;
3.通知binder driver(BD)使SM成为BD的context manager;
4.维护一个死循环,在这个死循环中,不停地去读内核中binder driver,查看是否有可读的内容;即是否有对service的操作要求, 如果有,则调用svcmgr_handler回调来处理请求的操作。
5.SM维护了一个svclist列表来存储service的信息。
这里需要声明一下,当service在向SM注册时,该service就是一个client,而SM则作为了server。而某个进程需要与service通信时,此时这个进程为client,service才作为server。因此service不一定为server,有时它也是作为client存在的。
三、ProcessState
ProcessState是以单例模式设计的。每个进程在使用binder机制通信时,均需要维护一个ProcessState实例来描述当前进程在binder通信时的binder状态。
ProcessState有如下2个主要功能:
1.创建一个thread,该线程负责与内核中的binder模块进行通信,称该线程为Pool thread;
2.为指定的handle创建一个BpBinder对象,并管理该进程中所有的BpBinder对象。
(一)Pool thread
在Binder IPC中,所有进程均会启动一个thread来负责与BD来直接通信,也就是不停的读写BD,这个线程的实现主体是一个IPCThreadState对象,下面会介绍这个类型。
下面是 Pool thread的启动方式:
ProcessState::self()->startThreadPool();
(二) BpBinder获取
BpBinder主要功能是负责client向BD发送调用请求的数据。它是client端binder通信的核心对象,通过调用transact函数向BD发送调用请求的数据,它的构造函数如下:
BpBinder(int32_t handle);
通过BpBinder的构造函数发现,BpBinder会将当前通信中server的handle记录下来,当有数据发送时,会通知BD数据的发送目标。
ProcessState通过如下方式来获取BpBinder对象:
ProcessState::self()->getContextObject(handle);
在这个过程中,ProcessState会维护一个BpBinder的vector mHandleToObject,每当ProcessState创建一个BpBinder的实例时,回去查询mHandleToObject,如果对应的handle已经有binder指针,那么不再创建,否则创建binder并插入到mHandleToObject中。
ProcessState创建的BpBinder实例,一般情况下会作为参数构建一个client端的代理接口,这个代理接口的形式为BpINTERFACE,例如在与SM通信时,client会创建一个代理接口BpServiceManager。
四、IPCThreadState
IPCThreadState也是以单例模式设计的。由于每个进程只维护了一个ProcessState实例,同时ProcessState只启动一个Pool thread,也就是说每一个进程只会启动一个Pool thread,因此每个进程则只需要一个IPCThreadState即可。
Pool thread的实际内容则为:
IPCThreadState::self()->joinThreadPool();
ProcessState中有2个Parcel成员,mIn和mOut,Pool thread会不停的查询BD中是否有数据可读,如果有将其读出并保存到mIn,同时不停的检查mOut是否有数据需要向BD发送,如果有,则将其内容写入到BD中,总而言之,从BD中读出的数据保存到mIn,待写入到BD中的数据保存在了mOut中。
ProcessState中生成的BpBinder实例通过调用IPCThreadState的transact函数来向mOut中写入数据,这样的话这个binder IPC过程的client端的调用请求的发送过程就明了了。
IPCThreadState有两个重要的函数,talkWithDriver函数负责从BD读写数据,executeCommand函数负责解析并执行mIn中的数据。
五、主要基类
(一)基类IInterface
为server端提供接口,它的子类声明了service能够实现的所有的方法;
(二)基类IBinder
BBinder与BpBinder均为IBinder的子类,因此可以看出IBinder定义了binder IPC的通信协议,BBinder与BpBinder在这个协议框架内进行的收和发操作,构建了基本的binder IPC机制。
(三)基类BpRefBase
client端在查询SM获得所需的的BpBinder后,BpRefBase负责管理当前获得的BpBinder实例。
最后
Android学习是一条漫长的道路,我们要学习的东西不仅仅只有表面的 技术,还要深入底层,弄明白下面的 原理,只有这样,我们才能够提高自己的竞争力,在当今这个竞争激烈的世界里立足。
本文在开源项目:我的GitHub
中已收录,里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中...
这次就分享到这里吧,下篇见。
本文转自 https://www.jianshu.com/p/5cabe499922c,如有侵权,请联系删除。