Binder是一种IPC通讯机制,在android系统中处于核心地位,几乎全部的跨进程通讯,或者进程内部系统组件通讯都是经过Binder进行交互。 Binder如同其字面意思,将系统各部件粘结起来,造成一个有机的总体。若是不能很好地理解Binder,就确定不能很好地理解android系统内部运行机制。Binder基于C/S架构而设计,Binder服务端注重优质服务的实现,Binder客户端只需向服务端按事先约定好的协议访问Binder服务端便可,而具体的通讯通道创建、请求发起、请求接入、呼应返回由相应的Binder通讯模块完成。java
图1描述了Binder架构简图,图中将Binder架构分为Framwork、Library及Kernel三大部分,该三大部分也是对应于Android系统的Framework、Library和Kernel。android
图1 Binder架构简图程序员
Binder Framwork架构
包含Camera服务、Activity服务、Window服务、Power管理服务等,是App开发人员最熟悉的一层。app
Framework左边包括ActivityManager、WindowManager等系统服务客户端,经过Context.getSystemService方法取得,每一个Manager内部定义了一个BinderProxy,用于向远端Binder服务发起IPC调用,并接收处理结果。框架
Framework右边所括ActivityManagerService、WindowManagerService、PowerManagerService等Binder服务端,用于接收BinderProxy发来的IPC调用请求,并返回处理结果,这些service运行于SystemServer进程。ide
Binder Framework使用Java语言编写成,主要是为Application层应用提供API及系统服务。Binder Framework经编译打包后,位于framework.jar中。函数
Binder Librarygoogle
包含BpBinder、BBinder/JavaBBinder、ProcessState、IPCThreadState等组件。编码
其中BpBinder对应Binder Framework的BinderProxy,由JNI层的android_util_BinderProxy进行联结,做用是将Binder启用Binder驱动传输调用方法代码、调用参数以及等待IPC调用返回结果。
BBinder (JavaBBinder)对应Binder Framework的Binder,当BBinder接收到Binder客户端调用请求后,经过JNI
调用将调用请求转交给Binder Framework的Binder对象,而后等待Binder对象返回处理结果,再将结果转交给底层传输回Binder客户端。
ProcessState和IPCThreadState是Binder框架底层通讯重要的两个类,可归属于Binder HAL层的内容。
每个App或进程,对应惟一一个ProcessState实例,该实例持有当前进程与Binder驱动的通讯状态,定义了非系统App的Binder空间大小、Binder线程数等信息,负责打开、维护、关闭/dev/binder设备。ProcessState在android_util_Binder.cpp中的android_util_BinderInternal的方法中初始化。
图2描述了 ProcessState初始化的代码,其主要工做是打开Binder驱动设备,而后将Binder设备mmap到当前进程的地址空间,地址空间范围通常为1MB-8KB,起始地址由Linux系统调用mmap自行根据当前进程状况肯定。
图2 ProcessState初始化代码
图3描述了ProcessState打开Binder驱动设备的过程。
图3 ProcessState打开Binder设备代码
IPCThreadState记录了IPC线程的状态,用于与系统内核中的Binder驱动进行具体的交互通讯,通讯方式使用Linux系统调用ioctl。每接收到一个新的Binder客户端请求,Binder服务端会从新创建一个线程和IPCThreadState记录对该Binder客户端的通讯,最大IPC线程数为15,在ProcessState中定义,见图4。
图4 ProcessState常量定义
Binder Kernel
该部分模块运行于Linux系统内核,包含Binder驱动,Binder Library与Binder Kernel使用ioctl进行读写操做。
相比较其余的IPC通讯,好比消息机制、共享内存、管道、信号量等,Binder仅需一次内存拷贝,便可让目标进程读取到更新数据,同共享内存同样至关高效,其余的IPC通讯机制大多须要2次内存拷贝。
图5描述了Binder内存拷贝的原理示意图,进程A为Binder客户端,在IPC调用前,需将其用户空间的数据拷贝到Binder驱动的内核空间,因为进程B在打开Binder设备(/dev/binder)时,已将Binder驱动的内核空间映射(mmap)到本身的进程空间,因此进程B能够
直接看到Binder驱动内核空间的内容改动。由于Windows/Linux系统,内核空间范围大都为1GB,因此内核空间通常比较有限,因此Binder驱动的内存地址空间也相对较小。
图5 Binder内存拷贝示意图
在XX项目中,Binder异常致使的崩溃时不时会出现一次,图6为一例Binder崩溃日志。
图6 Binder崩溃日志
Binder崩溃,究其缘由是Google在android 6.0后,调整了Framework层在捕获到Binder通讯过程当中产生的异常的错误处理机制,android 6.0以前,Framework捕获到binder通讯的异常,并不将异常再抛给应用端,而6.0以后,Framework捕获到binder通讯异常,转而从新包装一下该异常为新的异常,再将新异常从新抛出,其目的是让应用端能更好地更合理地对Binder通讯异常的处理。
图7为ActivityManager.getRunningAppProcesses方法的7.1版本实现,很明显方法在捕获到RemoteExcetion后转抛出DeadSystemExcetion。
图8为ActivityManager.getRunningAppProcesses方法的6.0版本实现,方法捕获到RemoteException后返回null。
图7 AcitivityManager方法的anroid 7.1版本
图8 AcitivityManager方法的anroid 6.0版本
图9分析了图6崩溃日志更深一些的方法调用栈,DeadObjectException等异常抛出点在android_util_Binder.cpp中的signalExceptionForError函数。
图9 Binder崩溃方法调用栈示例
图10描述了android_util_Binder.cpp中的signalExceptionForError函数定义。
图10 Binder异常产生源头代码
图11描述了android_util_Binder.cpp中的android_os_BinderProxy_transact函数,即Binder.java中BinderProxy.transactNative的C/C++实现。
图11 Binder异常产生源头代码
图12描述了Binder问题的汇总状态。从反馈的log信息概括,白牌项目遇到的崩溃集中在DeadObject,为图12中黄色背景部分的内容,另一个Binder异常是TransactionTooLargeException,是咱们本身为重现Binder崩溃而遇到的异常。
图12 Binder问题汇总
Binder崩溃问题之因此复杂,除了Binder自己设计及架构的复杂,还有使用模块及使用方式的复杂性,对于排错、寻找解决方案增长了很大的困难。图13显示了App代码三种主要的调用Binder路径,第一种是App直接Binder调用系统服务;第二种方式App先调用Android SDK,SDK再Binder调用系统服务,而系统服务可能Binder回调App接口,也有可能Binder调用其余系统服务,或者调用SDK API;第三种是App调用本身定义的Binder服务,自定义服务再Binder调用系统服务,或者调用SDK API。
图13展现了Binder调用的广度和调用层次的深度,其调用深度甚至可达到无究大,意味着圈定Binder问题可能的发生地或统筹Binder问题发生地分布,几乎是件不可能的事。所以,缓解Binder问题比较好的思路是拦截思路或在dalvik修改相关类的字节码。
图13 Binder问题产生路径
采用IBinder.linkToDeath注册IBinder.DeathRecipient回调。
大体思路是Binder客户端调用IBinder的linkToDeath方法注册回调IBinder.DeathRecipient,以便于当Binder服务端挂掉时,可即时收到Binder服务端崩溃的消息,此后Binder客户端可启动错误处理机制,或等待Binder服务端恢复运行,而后再继续访问。但该方案有一个盲点,如图14所示,当Binder服务端崩溃时,因为是C/S架构,服务端崩溃消息到达客户端须要必定的时间,若是客户端在服务端崩溃消息到达前,仍继续IPC调用服务端接口,则仍然有可能收到DeadObjectException,这是该方法的盲点。另外,使用此方案,也要求App在设计之初就要作相应的容错处理机制,而这种机制是大多数老应用程序所不俱备的,究其缘由是Binder机制虽是系统重要的机制,但其被各类上层封装所掩埋,大多数程序员不易直接接触Binder机制或Binder机制引出的问题,因此在设计、编写程序时不会考虑到Binder服务端挂掉时的错误处理,这种处理和通常的异常处理是不同的,涉及到跨进程访问,以及UI即时响应,以避免ANR等方面。
系统服务间大多也采用该方案监听远端服务的运行状态,好比SurfaceFlinger监听WindowManagerService。
该方案因引入成本过高,目前暂不引入项目,不过对App后续新模块的开发有指导意义,可基于方案写出一套完善的、基于IPC调用的适于android系统的较强鲁棒性的功能模块。
图14 Binder C/S架构蔽端
图15为方案一的代码示意。
图15方案一代码示意
使用Hook拦截Framework层服务异常。
思路
使用InvocationHandler动态代理系统服务类,在此基础上try-catch住系统服务的RemoteException。图16中类ServiceInterceptor的invoke方法为本方案核心,主要思路是在IPC调用前使用Binder.flushPendingCommands释放Binder内存,而后进入try-catch内动态代理调用系统服务方法,若是系统服务抛出异常,流程则转至catch(RemoteException rex)处理,最后在invoke方法返回前再次调用Binder.flushPendingCommands清理Binder内存。
方案优势
能够捕获系统服务异常并处理。
方案缺点
Invoke方法在捕获到异常后,返回null值或0值给调用者,若是调用者没有对返回结果进行校验,有可能会致使NullPointerException或业务逻辑不正确。
图16方案二代码示意
方案三
同方法二,只是拦截异常地点不一样,是在BinderProxy的transact方法进行拦截。
BinderProxy的transact方法是Binder客户端的消息集散地,如同Handler的handleMessage,在此截获消息,首先是能够掌握进出本应用的消息流动态,其次是方便拦截和蔼后处理。
图17为BinderProxy的类定义,对于Binder崩溃问题,transact方法是关键,DeadObjecException是由transact内部部调用JNI方法transactNative产生,在transact捕获DeadObjectException,实为java层第一时间捕获,防止该异常继续向java层发散。
图17 BinderProxy类定义
图17为AIDL封装Binder客户端的代码示意,mRemote为BinderProxy实例,从图18中可看出,AIDL封装并不关心mRemote.transact的返回状态值,具体值包装在_reply中。
图18 AIDL客户端方法示意
图19、图20为如何拦截Ams中BinderProxy.transact方法的代码示意。注意,每一个服务的编码结构不同,须要适当调整代码以拦截BinderProxy异常。
图19拦截代码示意
图20 拦截代码示例
方案四:
该方法是逆向工程的思路,须要了解Dalvik内部运行机制,采用静态/动态修改dex字节码方式,修改BinderProxy的transact方法。
Binder崩溃问题,反映的是Android6.0之后的版本一改以前各版本处理方式,Google将Framework层Binder调用产生的异常直接抛给App层,让Android6.0之前的写好的App猝不及防,并且google也没有明确提出这方面的最佳实践建议或解决方案,目前为止,还没有找到行业内的公认解决方案,可能缘由是Android6.0以前,市面上主要互联网公司的产品早已面世,如今无非是迁移到新的系统版本而已,彻底基于Android6.0的app或新产品不多。各App厂商或多或少都会遇到Binder问题,取决于手机硬件环境和手机厂商对android系统的定制深度。我感受各App厂商如今都在寻求一种解决Binder崩溃的方案,只不过在大多数App厂商来看,Binder问题崩溃的状况较少,其涉及模块和缘由比较复杂,和系统软硬件有关,因此他们暂时将Binder问题纳为待研究问题状态,能够暂时容忍少许的App崩溃现像,由于不像白牌项目受到客户的严格稳定性指标所限。