Binder 是 Android 中使用最广泛的 IPC 机制。
- Binder 驱动(路由器)
- Service Manager(DNS)
- Binder Client(客户端)
- Binder Server(服务器)
6.1 智能指针
解决C/C++项目中常见的指针问题:
- 指针没有初始化
- new 了对象后没有及时 delete
- 野指针
6.2 进程间的数据传递载体 -- Parcel
Parcel 是一种数据的载体,具备打包和重组的能力,用于通过 IBinder 传递信息。
Parcel 可读写如下类型数据:
- 原始数据类型以及原始数据类型数组。
- 实现 Parcelable 接口的对象(Bundles)。
- Active Objects(读取的是写入的实例,主要有两类:Binder,FileDescriptor)。
- Untyped Containers(标准的任意类型的 Java 容器)。
值得注意的是,Parcel 存/取数据的方式都是一一对应的,如 writeByte(byte)/readByte()。
Binder驱动与协议
Android 系统是基于 Linux 内核的,因而 Binder 驱动也是标准的 Linux 驱动。具体而言,Binder 驱动会把自己注册成一个杂项设备(misc device),并向上层提供 /dev/binder 节点,但它并不对应真实的硬件设备。Binder 驱动运行于内核态,可提供 open()、ioctl()、mmap() 等常用文件操作。
Binder 驱动为上层提供了6个文件操作接口,分别为 binder_poll,binder_ioctl,binder_mmap,binder_open,binder_flush,binder_release。其中 binder_ioctl 实现了应用进程与 Binder 驱动之间的命令交互,承载了 Binder 驱动大部分业务。
ServiceManager(Binder Server)
SM 是在 init 程序解析 init.rc 时启动的,自身也是个 Binder Server(xx Service)。
SM 处理请求过程:
- 从 Binder 驱动读取消息
- 处理消息
- 不断循环,而且永远不会主动退出(除非出现致命错误)
SM 内部维护一个 svclist 列表,用户存储所有 Server 相关信息(以 svcinfo 为数据结构),查询和注册都是基于这个表展开的。
访问 SM(Binder Server)服务的流程:
- 打开 Binder 设备;
- 执行 mmap;
- 通过 Binder 驱动向 SM 发送请求(SM 的 handle 为0);
- 获得结果
ServiceManagerProxy
当某个 Binder Server 启动时,会把自己的名称 name 与对应的 Binder 句柄保存在 SM 中。调用者通常只知道 Binder Server 的名称,所以必须先向 SM 发起查询请求,就是 getService(name)。
SM 自身也是一个 Server(句柄值为0),因而任何 Binder Client 都可以直接通过0这个 Binder 句柄创建一个 BpBinder,在通过 Binder 驱动去获取 SM 的服务。具体而言,就是调用 BinderInternal.getContextObject() 来获得 SM 的 BpBinder。BpBinder 在 Java 层以 IBinder 来表示,对 SM 而言,IBinder 的真正持有者与使用者是 ServiceManagerProxy ,它是 SM 在本地的代表。
ProcessState 与 IPCTheadState
Android 系统为程序进程使用 Binder 机制封装了两个实现类,ProcessState 与 IPCTheadState。ProcessState 与进程相关,负责打开 Binder 驱动设备,进行 mmap() 等准备工作;IPCTheadState 与线程相关,负责与 Binder 驱动进行具体的命令通信。
在 getService() 这个场景中,调用者是从 Java 层的 IBinder.transact() 开始,层层往下调用到 IPCThreadState.transact(),然后通过 waitForResponse 进入主循环,直到收到 SM 的回复后才跳出循环,并将结果再次层层回传到应用层。
Binder 驱动
Binder 驱动只需要一次复制就可以把数据从一个进程复制到另一个进程。Binder 中还保存着大量的全局以及进程相关的变量,用于管理每个进程/线程的状态、内存申请和代办事项等一系列复杂的数据信息。
Service Manager 的实现
SM 在 Android 系统启动之后就运行起来了,并通过 BINDER_SET_CONTEXT_MGR 把自己注册成 Binder “大管家”。它在做完一系列初始化后,在最后一次 iocl 的 read 操作中会进入睡眠等待,直到有 Binder Clint 发起服务请求而被 Binder 驱动唤醒。
SM 唤醒后,程序分为两条主线索:其一,SM 端将接着执行 read 操作,把调用者的具体请求读取出来,然后利用 binder_parse 解析,再根据实际情况填写 transaction 信息,最后把结果通过 BR_REPLY 命令(也是 ioctl)返回 Binder 驱动。
其二,发起 getService 请求的 Binder Client 在等待 SM 回复的过程中会进入休眠,直到被 Binder 驱动再次唤醒 -- 它和 SM 一样也是在 read 中睡眠的,因而醒来后继续执行读取操作。这一次得到的就是 SM 对请求的执行结果。程序先把结果填充到 reply 这个 Parcel 中,然后通过层层返回到 ServiceManagerProxy,再利用 Parcel.readStrongBinder 生成一个 BpBinder,最终经过类型转换为 IBinder 对象后传给调用者。
Binder客户端 -- Binder Client
binderService 启动系统其它进程提供的 Service :
- 应用程序填写 Intent,调用 bindService 发出请求。
- 收到请求的 bindService 将与 ActivityManagerService(AMS)取得联系。
- AMS 基于特定的“最优匹配策略”,从其内部存储的系统所有服务组件的资料中找到与 Intent 最匹配的一个,然后向它发送 Service 绑定请求,如果目标进程还不存在的话,AMS 还要负责把它启动起来。
- “被绑定”的服务进程需要相应绑定,执行具体操作,并在成功完成后通知 AMS;然后由 AMS 再回调发起请求的应用程序。
Android接口描述语言 -- AIDL
AIDL 是 Android Interface Description Language 的简写,是用于定义客户端/服务端通信接口的一种描述语言。
构建一个 Binder Server 所需工作如下:
- 启动的时机
- 提供一致的服务接口
- 与 Binder 驱动的交互方式
- 外界如何能访问到这个 Server 的服务
方式一:Server 在 ServiceManager 中注册。
方式二:通过其他 Server 作为中介(匿名Server)。
AIDL的关键字:
oneway 关键字用来同步实现远程异步调用 aidl 服务。使用该关键字时,远程调用不会阻塞;它只是发送事务数据并立即返回。接口的实现最终接收此调用时,是以正常远程调用形式将其作为来自 Binder 线程池的常规调用进行接收。 如果 oneway 用于本地调用,则不会有任何影响,调用仍是同步调用。
所有非原语参数都需要指示数据走向的方向标记,默认为 in。
- in:方法参数数据只能从客户端单向流向服务端。
- out:方法参数数据只能从服务端单向流向客户端。
- inout:方法参数数据既可以从客户端单向流向服务端,也可以从服务端单向流向客户端。