前景提要:
关于Binder相信大多数Android开发者都会多多少少了解一些,但很少有人可以拍着胸脯说“我了解Binder的所有细节”,当然这篇文章也不是要写所有的Binder知识,只是要记录一下我对于Binder的一些认知以及一些常见的关于Binder的知识和问题。
这篇文章我讲通过四个方面来总结Binder的知识:1. 基础 2. 原理 3. 应用 4. 常见问题
基础
在我们了解一个事物的时候,通常需要知道这个事物的几个方面:1. 是什么?干什么用的? 2. 在哪里用? 3. 为什么要用它?有什么优势?下面我们就通过这几个方面来了解。
是什么?干什么用的?
首先Binder它不是一个单一的角色,它在Android系统中扮演着多个角色。所以就要根据不同的角色来看。
角色 | 定义 | 作用 |
---|---|---|
通信 | IPC通信协议 | 模糊进程边界,跨进程通信 |
传输 | 跨进程传输数据 | 把数据从一个进程传递到另一个进程 |
驱动 | 不同事物的连接 | 用户空间和内核空间的连通 |
简单说明:
要知道某个事物的定义和功能,要根据它所在的环境。Binder是Google专门给Android开发的IPC的方式。它是基于C/S架构的,在驱动的作用下连接Client和Server,实现Client和Server之间的数据通信。关于具体的原理和在Android中的使用在后面会介绍。
在哪里用?
首先是在Android系统中使用的,但是Android中并不是只使用这一种IPC通信方式,Android是基于Linux的,Linux有多种IPC的通信方式,Android中也用用到。
比如Zygote孵化进程的时候使用的是Socket,kill_process使用的是信号的方式。Binder主要使用在APP间以及APP和Framework之前。
为什么要用它?有什么优势?
首先进程间数据不共享,而有时候需要进程间来传输数据,所以就需要用到IPC技术。但是IPC技术有很多种,比如:管道、Socket、共享内存等等,那为什么要用Binder呢?这时候就要看它有什么优势,可以从几方面来看:
方面 | 优势 |
---|---|
性能 | 一次数据拷贝,节省性能消耗 |
稳定 | 基于C/S架构,职能清晰 |
安全 | 有UID/PID来进行唯一标识,通信可以识别来源进行权限控制 |
语言 | Android开发使用面向对象的JAVA语言,更适合 |
战略 | Linux内核开源,为了避免上层调用内核也需要开源,实现特殊方式调用 |
简单说明:
为什么使用Binder并不是通过单一方面的优势决定的,而是多方面综合起来它最合适。比如性能方面是一次数据拷贝,而共享内存是0次数据拷贝,所以性能方面并不是最高的,但是基于它的稳定架构以及主要的解决安全问题和其它种种优势综合起来,Binder的优势就很明显。
原理
作为一个久经沙场的开发者,势必要懂得一些知识点的原理。这样才能更好的理解这个知识点是怎么工作的,不谈改进,但最起码可以更好的利用它来进行功能的开发或改进。关于实践方面在后面也会介绍。
相关模块及作用
来个IPC的全家桶 Binder Client、Binder Server、Binder Driver (Binder 驱动)、ServiceManager 分布图
上图应该很清楚的说明了Client和Server进行IPC通信所参与的角色,以及他们之间是如何配合。这里就不再多做说明。下面我们按顺序拆解开来说明每一个角色所执行的详细操作:
1. ServiceManager 的作用
它是连接其它三个模块的桥梁,相当于一个配置管理中心。下面分开来说
- 进程使用 BINDERSETCONTEXT_MGR 命令将自己注册成 ServiceManager,Binder Driver 会为其创建Binder实体,并且每个 Client 中都有其引用
- 跟 Binder Driver 通信的时候是通过系统调用的方式来进行
- 跟 Client 和 Server 通信的时候,它都是作为服务端的角色
2. Client 的作用
Client 是相对的一个概念,就是要请求服务的一方。它会使用 BinderProxy 作为 Server Binder 实体的一个代理对象来进行服务端服务的调用。
3. Server 的作用
同样 Server 也是相对的一个概念,就是要提供服务的一方。它会创建一个 Binder 实体,并提供服务供 Client 来调用。
4. Binder Driver 的作用
Binder 并不是 Linux 系统内核的一部分,那怎么进行数据中转呢?这就得益于 Linux 的动态内核可加载模块(Loadable Kernel Module,LKM)的机制;模块是具有独立功能的程序,它可以被单独编译,但是不能独立运行。它在运行时被链接到内核作为内核的一部分运行。这样,Android 系统就可以通过动态添加一个内核模块运行在内核空间,用户进程之间通过这个内核模块作为桥梁来实现通信。 在 Android 系统中,这个运行在内核空间,负责各个用户进程通 过 Binder 实现通信的内核模块就叫 Binder 驱动(Binder Dirver)。
上面三个模块都是在用户空间的,它们之间的交互都需要通过 Binder Driver 来进行中转。比如 Client 要请求服务,首先要在 ServiceManager 进行服务查询,这个时候 Client 要告诉 Binder Driver 它想要访问 ServiceManager 然后 Binder Driver 来通知 ServiceManager 并调用其查询服务,查询完成后通知 Binder Driver 然后 Binder Driver 在把服务地址给 Client。
调用过程
上面介绍了IPC过程中的几个模块以及他们之间的作用。下面来具体展开看他们之间是怎么交互的。还是先来一张调用关系图
这张图描述了 IPC 过程中四个模块是怎么交互的,都是需要 Binder Driver 来作为中间者来中转其它三个模块的通信。其中不同的过程 Client 和 Server 的角色是动态切换的,比如 Server 请求 ServiceManager 注册服务的时候它是作为 Client 的角色使用 ServiceManager 的 BinderProxy 来进行注册,而 Client 来请求 Server 的服务的时候它就是作为 Server 的角色来提供服务调用。
数据传输
上面说了各个模块的调用过程,在调用过程中数据是怎么传输的呢?下面还是先放一张图来介绍
正常情况下数据传输是需要两次数据拷贝的,首先发送方把数据拷贝到内核空间,然后内核再把数据拷贝给接收方。之前提过 Binder 机制只有一次数据拷贝,它是通过内存映射的方式来实现的,首先在内核空间创建一块数据接收缓存区(大小是1M,先埋个坑,后面会说这块的应用),然后跟内核缓存区和数据接收方分别建立映射关系,这样发送方在发送数据到内核缓存区的时候由于映射关系的存在,相当于直接把数据发送给了接收方。数据传输的过程只发生了一次数据拷贝。
关于内存映射多说一点题外话。它通常是在有物理介质的文件系统中使用的,比如用户进程是不能访问物理设备的,如果要把磁盘上的文件读到用户进程,需要两次拷贝(磁盘-->内核空间-->用户空间)通过在物理介质和用户空间之间建立映射,减少数据的拷贝次数,用内存读写取代 I/O 读写,提高文件读取效率。
原理总结
上面分步骤介绍了 Binder 机制 IPC 的涉及模块、调用、数据传输。下面来一个总结图
Client 通过 ServiceManager 或 AMS 获取到的远程 binder 实体,一般会用 Proxy 做一层封装,比如 ServiceManagerProxy、 AIDL 生成的 Proxy 类。而被封装的远程 binder 实体是一个 BinderProxy。
BpBinder 和 BinderProxy 其实是一个东西:远程 binder 实体,只不过一个 Native 层、一个 Java 层,BpBinder 内部持有了一个 binder 句柄值 handle。
ProcessState 是进程单例,负责打开 Binder 驱动设备及 mmap;IPCThreadState 为线程单例,负责与 binder 驱动进行具体的命令通信。
由 Proxy 发起 transact() 调用,会将数据打包到 Parcel 中,层层向下调用到 BpBinder ,在 BpBinder 中调用 IPCThreadState 的 transact() 方法并传入 handle 句柄值,IPCThreadState 再去执行具体的 binder 命令。
由 binder 驱动到 Server 的大概流程就是:Server 通过 IPCThreadState 接收到 Client 的请求后,层层向上,最后回调到 Stub 的 onTransact() 方法。
当然这不代表所有的 IPC 流程,比如 Service Manager 作为一个 Server 时,便没有上层的封装,也没有借助 IPCThreadState,而是初始化后通过 binder_loop() 方法直接与 binder 驱动通信的。
应用
上面介绍了 Binder 的基础和原理,下面说一些在 Andorid 中有哪些地方使用了 Binder 技术和我们怎么使用 Binder 技术。
系统中的使用
四个组件中的链接纽带 Intent 就是通过 Binder 方式进行 IPC 通信的。AIDL 是对它的一个封装。比如 startActivity 就是启动 Activity 所在的 APP 进程与 AMS 系统进程以及目标 Activity 所在 APP 进程之间的通信,包括启动和数据传递。
开发者的使用
开发者怎么使用 Binder 技术,Android 给我们封装了 AIDL 所以开发者通常使用这种方式来进行 IPC 通信。还有基于 AIDL 封装的 Messenger 它是串行运行,所以Messenger一般用作消息传递。
简单说一下 AIDL 的流程:
- Server 端写 .aidl 文件,里面是可提供的服务方法,在编译时系统会为我们生成一个接口实现IInterface。
- Server 写一个 Service 来提供服务,创建 Stub(它是接口的一个实现抽象实现类) 实例并实现接口中的服务方法,在 onBind 中返回 Stub 实体对象(Stub 实体对象就是服务端的 Binder 实体)
- 将服务端编写的 .aidl 文件拷贝到 Client 中,表明 Client 和 Server 使用的是同一个协议
- Client 端通过 bindService 的方式来绑定 Server 的 Service。方法中需要传入 ServiceConnection 对象。在 ServiceConnection 对象的回调函数 onServiceConnected 中可以拿到 Binder Driver 提供的由 Server 的 Service 在 onBind 中返回的 Binder 实体对象。
- 我们通过 Stub 的 asInterface 方法传入上一步的 Binder 实体对象拿到对应 Binder 实体对象的代理对象 Proxy 它实现了服务接口
- 上一步 Client 拿到了接口的实例对象 Proxy 因为只暴露接口不用关注具体实现,通过接口去访问 Server 的服务方法。
常见问题
前文中提到了内核空间的接收数据缓存区的大小是 1M 这里会引出一些问题,比如 Intent 传输数据的大小限制。因为 Intent 传输数据是通过 Binder 的机制来实现的,所以就被限制在 1M 的大小,但是内核中接收数据缓冲区是共享的,所有通过 Binder 机制进行通信的都会共享这块接受缓冲区,如果同时有多个进程在使用,这个时候多个进程就会共用这块缓冲区。所以 Intent 传输数据的大小限制不是一个固定的值。