Android 面试考 Binder,看这一篇就够了

简介

Android 的世界中,每个应用都运行在自己的进程中,都拥有着一个独立的Dalvik虚拟机实例,每一个实例都作为着一个独立的进程执行,基于着这种进程"沙箱"机制,Android 得以将保证各个应用各司其职,不轻易的受到来自外部环境的安全隐患干扰(当然沙箱机制还是不够的,Anroid还有Cap、SELinux等来保证系统安全)。

然而,作为一个完整的应用,又不可避免的需要同外部的应用进行数据交换。比方说应用想要在系统成功启动后做某些初始化的操作,那么应用就可以选择动态或者静态的注册一个广播,待系统成功启动后,system_server进程会通过广播将数据发送到我们应用,最后调用到onReceive方法;又比方说,在从用户请求到相关的权限后,我们想要获取到手机中的联系人列表信息,那么应用就可以通过传入正确的Uri,然后从ContentProvider.query方法返回的Cursor对象中一一读取出联系人信息。初级以上的开发者都知道,这些操作都是四大组件的基本功能,而这些功能的实现主要就是依赖了Binder进程间通信的能力。可以这么说,从用户最常见的Activity到开发者们常用到的AIDL,再到系统的其它生僻模块,Binder的足迹无处不在。

Binder的重要性我就不再多讲。只说在面试中,无论是面安卓应用开发还是安卓系统开发,倘若能够和面试官在谈论Binder时谈笑风生,至少会给面试官一个良好的技术印象。

今年年中的时候我又重读了老罗的那本经典,并将Binder源码和对应注释上传到了GitHub: Binder_SourceCode,如果大家觉着这些面试题不全面或者想要看更多细节的话,可以当参考看看~

那么接下来我们就开始从各个面试题来了解Binder的方方面面,这些面试题我会不断的补充,也欢迎大家对其中的错误进行指正

Android O上对Binder做了较大的改动,这些关于新特性的问题以后我会补上~

Binder 的概念?

  • Binder 的前身是 OpenBinder,它是先后由Be和Palm公司进行开发的IPC开源系统,非常适合运行在小型手持设备上,现在由谷歌负责开发并一直被用作Android系统的主要IPC手段
  • "binder" 这个单词有着粘合剂的意思,寓意为一个系统的主干,可以将系统中的不同模块粘合成一个整体
  • Android 系统中每个进程都维护着一个线程池用来响应别的进程的请求;Binder 框架则负责管理对象间的引用计数、内核空间缓冲区和通信模型等
  • Binder 使用 ServiceManager 来管理各个系统服务;当进程的服务被注册时, binder_node 实体节点会被插入到内核空间的红黑树中,ServiceManager 则会在进程的内核空间中添加ref引用

Linux 原有的IPC机制为何不用,却偏偏使用Binder呢?

  • 传输性能:Binder 只需拷贝一次数据;虽然共享内存只需一次,但是使用起来很复杂
IPC 方式 数据拷贝次数
Binder 1
Socket/消息队列/管道 2
共享内存 0
  • 安全性

传统的IPC方式没有严格的安全措施;比方说Socket,IP地址可以由客户端手动填入,调用者的身份很容易进行伪造

Android会为每个已安装的应用程序添加属于自己的UID,Binder 基于此会根据请求端的UID来鉴别调用者的身份,保障了安全性

  • 易用性

Binder 使用的是C/S通信方式,一个进程可以开启服务专门负责处理某个模块的业务,多个进程可以作为Client同时向Server发起请求

使用了面向对象的设计,发起一次binder call就像在调用本地方法一样简单

Binder Driver 如何在内核空间中做到一次拷贝的?

当Client向Server发送数据时,Client会先从自己的进程空间把通信数据拷贝到内核空间,因为Server和内核共享数据,所以不再需要重新拷贝数据,而是直接通过内存地址的偏移量直接获取到数据地址。总体来说只拷贝了一次数据。

Server和内核空间之所以能够共享一块空间数据主要是通过binder_mmap来实现的。它的主要功能是在内核的虚拟地址空间申请一块和用户虚拟内存相同大小的内存,然后再申请一个page大小的内存,将它映射到内核虚拟地址空间和用户虚拟内存空间,从而实现了用户空间缓冲和内核空间缓冲同步的功能。

查看大图

四大组件中常见的2个 Binder 服务是?

一个是实现了IActivityManager接口的ActivityManagerNative,ActivityManagerService是它的子类,提供了AMS中的所有服务,从APP调用binder call到AMS都是同步的,APP需要阻塞等待AMS执行完毕

一个是实现了IApplicationThread接口的ApplicationThreadNative,ActivityThread中的内部类ApplicationThread是它的子类,AMS可以发起异步请求到APP,不需要等待APP执行完成

查看大图

BpBinder 与 BBinder 有什么区别?

查看大图

  • BpBinder: Binder的代理对象,内部有一个成员变量mHandle
  • BBinder: Binder 本地服务对象

binder_ref 与 binder_node 有什么区别

查看大图

  • binder_ref与binder_node都存在于内核空间
  • binder_node是实体对象、binder_ref是引用对象
  • binder_node被binder_ref引用,binder_ref被BpBinder引用

应用该如何获取和添加 Binder 服务?

获取与添加 Binder 服务的操作是交给"大管家"ServiceManager去做的;ServiceManager进程其启动后会通过 binder_loop 睡眠等待客户端的请求,如果进程被客户端唤醒就会调用 svcmgr_handler 来处理获取或者添加服务的请求

ServiceManager 也是一种 Binder 服务,应用该如何获取它的服务呢?

典型的一个鸡生蛋还是蛋生鸡问题

当应用获取ServiceeManager服务的代理时,它的handle句柄固定为0,所以才不需要去查找,具体见以下代码

  • private static IServiceManager getIServiceManager() {
  • if (sServiceManager != null) {
  • return sServiceManager;
  • }
  • // Find the service manager
  • sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
  • return sServiceManager;
  • }
  • sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/)
  • {
  • return getStrongProxyForHandle(0);
  • }

Binder 都有什么主要的协议?

查看大图

协议 意义
xx_TRANSACTION 传输请求数据
xx_REPLY 回复数据
xx_INCREFS 弱引用计数+1
xx_DECREFS 弱引用计数-1
xx_ACQUIRE 强引用计数+1
xx_RELEASE 强引用计数-1
BR_SPAWN_LOOPER Binder驱动通知接收方进程创建一个新线程
BR_TRANSACTION_COMPLETE Binder驱动通知发送方发送成功
BR_DEAD_REPLY 告知发送方服务进程已经死亡
BR_DEAD_BINDER 向发送方发送死亡通知
BC_ENTER_LOOPER 通知Binder驱动主线程已经进入循环
BC_EXIT_LOOPER 通知Binder驱动主线程已经退出循环
BC_REGISTER_LOOPER 通知Binder驱动新线程已经创建
BC_FREE_BUFFER 通知Binder驱动释放内存

Binder 协议中BC_与BR_开头的协议都有什么区别?

  • BC_: 全称Binder Command, 进程发送给Binder驱动数据时携带的协议
  • BR_: 全称Binder Return,Binder驱动发送给进程数据时携带的协议

Binder 服务在调用期间抛出了RuntimeException异常,服务端会Crash么?

服务端并不会Crash,RuntimeException被Binder服务端线程捕捉,随后将异常信息写入到reply中,发回Binder客户端进程,最后会客户端binder线程抛出这个异常,如果没有捕捉这个RuntimeException,那么Binder客户端进程会Crash

以下为AMS.startService出现异常为例进行讲解

  • // ---frameworks/base/core/java/android/app/ContextImpl.java
  • @Override
  • public ComponentName startService(Intent service) {
  • return startServiceCommon(service, mUser);
  • }
  • private ComponentName startServiceCommon(Intent service, UserHandle user) {
  • ComponentName cn = ActivityManagerNative.getDefault().startService(
  • mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
  • getContentResolver()), getOpPackageName(), user.getIdentifier());
  • }
  • // ---frameworks/base/core/java/android/app/ActivityManagerNative.java
  • public ComponentName startService(IApplicationThread caller, Intent service,
  • String resolvedType, String callingPackage, int userId) throws RemoteException
  • {
  • Parcel data = Parcel.obtain();
  • // 此时还在Binder客户端进程,发送binder call到AMS
  • mRemote.transact(START_SERVICE_TRANSACTION, data, reply, 0);
  • reply.readException();
  • return res;
  • }
  • // ---frameworks/base/core/java/android/os/Binder.java
  • private boolean execTransact(int code, long dataObj, long replyObj,
  • int flags) {
  • Parcel data = Parcel.obtain(dataObj);
  • try {
  • res = onTransact(code, data, reply, flags);
  • } catch (RemoteException e) {
  • } catch (RuntimeException e) {
  • if ((flags & FLAG_ONEWAY) != 0) {
  • Log.w(TAG, "Caught a RuntimeException from the binder stub implementation.", e);
  • } else {
  • reply.setDataPosition(0);
  • reply.writeException(e);
  • }
  • res = true;
  • } catch (OutOfMemoryError e) {
  • }
  • return res;
  • }
  • /* ---此时已经在AMS binder 服务端,onTransact会调用AMS.startService方法,如果发生了异常将会被捕捉---*/
  • // ---frameworks/base/core/java/android/os/Parcel.java
  • public final void writeException(Exception e) {
  • int code = 0;
  • // 异常转换成code
  • writeInt(code);
  • }
  • // ---frameworks/base/core/java/android/app/ActivityManagerNative.java
  • public ComponentName startService(IApplicationThread caller, Intent service,
  • String resolvedType, String callingPackage, int userId) throws RemoteException
  • {
  • Parcel data = Parcel.obtain();
  • mRemote.transact(START_SERVICE_TRANSACTION, data, reply, 0);
  • // 返回到Binder客户端,首先去查看是否有异常
  • reply.readException();
  • return res;
  • }
  • // ---frameworks/base/core/java/android/os/Parcel.java
  • public final void readException(int code, String msg) {
  • switch (code) {
  • // 此处省略了各种判断,先进行检查随后会抛出异常
  • case EX_SECURITY:
  • throw new SecurityException(msg);
  • ...
  • }
  • }

客户端调用 Binder 接口后抛出的DeadObjectException是什么意思?

以下代码是一般binder调用函数的常见写法,一般都需要捕捉到DeadObjectException与RemoteException

  • private void binderCallSample() {
  • try {
  • // xxx
  • } catch (DeadObjectException e) {
  • // xxx
  • } catch (RemoteException e) {
  • // xxx
  • }
  • }

之所以抛出DeadObjectException最常见的原因就是Binder服务端进程已经死亡。客户端进行binder调用时,Binder驱动发现服务端进程不能回应请求,那么就会抛出异常给客户端;

还有一些比较冷僻的原因,比如说服务端此时的缓存内存空间(1016K)已经被占满了,Binder驱动就认为服务端此时并不能处理这个调用,那么就会在C++层抛出DeadObjectException到Java层

Binder 驱动加载过程中有哪些重要的步骤?

查看大图

  • binder_init: 初始化Binder驱动环境,内核工作队列、文件系统节点、misc设备等
  • binder_open: 打开Binder设备,获取Binder驱动的文件描述符 (fd)
  • binder_mmap: 将用户进程地址空间映射到Binder驱动设备内存。这也是Binder能够实现内存一次拷贝依赖的根本
  • binder_ioctl: Binder驱动的核心功能,用来进行数据读写操作

Binder 的死亡通知机制的作用是什么,它是如何实现的?

Binder服务端进程死亡后,依赖着Binder实体对象的客户端代理对象也会失效。当Binder服务无效时,驱动程序会发送死亡通知给给各个已注册服务的客户端进程,以方便客户端做些销毁之类的操作

死亡通知机制最常用在APP与AMS服务,是典型的C/S架构,当APP端的进程死亡后,其ApplicationThreadNative会被销毁,随后Binder驱动会发出死亡讣告给AMS,方便清理已经失效了的四大组件及应用进程信息

查看大图

Binder 驱动的加载阶段会打开 /dev/driver 文件,当服务端的进程死亡后,系统会进入清理阶段,关闭所有与进程相关联的资源,这其中就包含了关闭 /dev/driver 文件描述符的操作,随后就会调用到 Binder 驱动的binder_release方法。经过层层调用最后会调用到 binderDied 这个回调方法。如果App进程之前注册并实现过 DeathRecipient 这个接口,此时App进程就能够对Server进程的死亡作出处理

bindService所绑定的"服务概念和Binder中的服务Server有什么区别"?

bindService绑定的服务所指的是四大组件中的Service; 调用context.bindService方法可以让Activity与Service形成一种"绑定"的概念,这个概念是在Android Framework定义,形成"绑定"的关系之后,Activity除了能够和Service进行通信之外,它们所在的进程存活状态也会相关联。注意,这个Service不确定是运行在本地还是远端的,大部分情况下我们调用startService只是用来启动一个在本地定义的Service组件。当使用bindService时,一般是需要调用Service通过onBind返回的Binder服务接口,以此实现Activity与Service之间的通信

Binder 的服务则是一个真正的C/S架构中的服务端角色。这个服务需要继承Binder类并实现一套服务接口才能生效。我们常见的ActivityManagerService正是一个继承了Binder类并运行在system_server的服务,用来提供四大组件以及进程方面的服务

注意,调用bindService之后,Service.onBind方法会返回Binder服务Stub对象,然后APP会在AMS进行登记,随后经过层层调用才会在Binder客户端调用onConnected方法,如果Binder客户端和其服务端在同一个进程中,客户端拿到的应该还是Stub对象,如果不再同一进程中,客户端拿到的应该是Binder服务的Proxy对象。

查看大图

top Created with Sketch.