Android Binder 机制详解

Binder 是 Android 系统中最核心的进程间通信(IPC)机制。

它解决的核心问题是:

一个进程如何安全、高效地调用另一个进程中的方法,并拿到执行结果?

例如,你在 Android 上通过 UIAutomator2 自动点击屏幕,看似只是执行了一句:

d.click(500, 800)

但 UIAutomator2 所在的进程并不能直接操作 Android 系统的输入服务,它需要通过系统提供的接口完成跨进程调用。

这背后就涉及 Binder IPC。

下面从原理、架构、调用过程、底层实现,以及 UIAutomator2 的实际使用场景来讲解。

1. 为什么 Android 需要 Binder?

Android 中存在大量独立的进程,例如:

进程 A:微信
进程 B:支付宝
进程 C:UIAutomator2
进程 D:system_server(Android 系统服务)

每个进程都有自己的虚拟地址空间。

假设 UIAutomator2 想获取当前屏幕上的 UI 控件信息:

  • UIAutomator2 运行在自己的进程中。

  • 系统的无障碍交互能力由其他系统组件提供。

  • UIAutomator2 不能直接读取系统服务的内存。

  • 它需要通过跨进程通信请求系统服务提供数据。

Binder 就是 Android 解决这个问题的主要机制。

它不仅能传递数据,还能实现类似普通函数调用的跨进程方法调用,也就是 RPC(Remote Procedure Call,远程过程调用)。

这里的“远程”不一定指另一台机器,也可以是同一台设备上的另一个进程。

2. Binder 的整体架构

Binder 可以分成四个核心部分:

四个核心角色:

组件作用
Client发起调用的进程
Server提供功能的进程
Binder Driver内核驱动,负责跨进程传递事务
ServiceManager管理 Binder 服务的注册与查询

在 Client 中,通常有一个 Proxy(代理对象);在 Server 中,则有对应的 Stub(服务端接收对象)。

这也是 Binder 很重要的一点:让跨进程调用看起来像调用本地方法。

3. 一次 Binder 调用是如何完成的?

假设进程 A 想调用进程 B 的一个方法:

int result = calculator.add(10, 20);

虽然看起来只是一次普通 Java 方法调用,但 calculator 如果是远程接口代理,底层实际要完成以下步骤。

这里涉及几个关键技术。

Parcel(序列化容器)

进程 A 不能直接把内存中的 Java 对象指针交给进程 B,因为这个指针在另一个进程中通常没有意义。

所以需要将参数编码成可传输的格式,例如:

方法编号:1
参数1:10
参数2:20

接收方再按照约定的格式解析参数。

Binder Driver(内核驱动)

Binder 在 Linux 内核中提供驱动,Android 通常通过 /dev/binder 访问它。

用户态 Binder 库通过系统调用,例如 ioctl(),与 Binder 驱动交互,进行事务提交、读取回复等操作。

Binder 线程池

Server 端通常使用 Binder 线程池处理跨进程请求。

因此,如果有多个进程同时调用一个服务,服务端可能有多个 Binder 线程并发执行请求。这也意味着服务端需要考虑线程安全问题。

4. Binder 和 AIDL 是什么关系?

这两个概念很容易混淆。

  • Binder 是底层 IPC 机制。

  • AIDL(Android Interface Definition Language)是定义跨进程接口的语言,并且能够生成 Binder 通信所需的代码。

例如,我们定义一个 AIDL 文件:

// ICalculator.aidl
interface ICalculator {
    int add(int a, int b);
}

AIDL 工具会生成支持跨进程调用的接口和相关代码。典型 Java 实现中涉及:

ICalculator
    |
    ├── Stub
    |    服务端接收 Binder 调用
    |
    └── Proxy
         客户端发送 Binder 调用

客户端可以像这样调用:

int result = calculator.add(10, 20);

而不必手动处理每个 Binder 事务。

可以把它们理解为:

Binder 负责把请求送过去,AIDL 帮助开发者定义请求是什么以及怎样处理。

5. Binder 有什么特点?

特点说明
高效专门针对 Android 本机进程间通信设计
安全可基于调用方 UID、PID 和系统安全策略进行权限检查
易用可封装为类似本地方法的调用
同步和异步支持同步事务和 oneway 异步事务
跨语言支持 Java、C++ 等语言环境

这里要注意,Binder 不是完全没有通信开销。序列化、线程调度、数据复制等仍然存在。

而且 Binder IPC 本身并不意味着调用方拥有目标服务的全部权限,服务端仍然可以拒绝未授权的操作。

6. 回到 UIAutomator2:Binder 在哪里?

这部分与你前面看到的「UIAutomator2 的全量 IPC」关系最大。

以开源项目 openatx/uiautomator2 为例,可以把操作链路简化为:

电脑
  |
  | Python: d.click(500, 800)
  |
  | HTTP / JSON-RPC
  v
Android 设备
  |
  | UIAutomator2 服务
  |
  | UiAutomator / UiAutomation API
  |
  | Binder IPC
  v
Android 系统服务
  |
  | 输入事件处理
  v
目标 App 接收到点击事件

这里有一个特别重要的区别:

Python 与设备端 UIAutomator2 服务之间的 HTTP/JSON-RPC 通信,不是 Binder。

Binder 主要出现在设备端与 Android 系统服务之间的跨进程交互。实际点击操作可能涉及输入管理相关服务,而 UI 层级信息读取则会涉及无障碍相关框架。具体调用链会随 Android 版本和操作类型变化。

因此,如果有人说:

采集 UIAutomator2 的全量 Binder IPC

技术上可能意味着希望观察其触发的 Binder 事务,例如:

  • 调用了哪些系统服务接口。

  • 对应的 Binder transaction code 是多少。

  • 请求参数和返回数据包含什么。

  • 调用来自哪个进程,发往哪个进程。

  • 某次 UI 操作触发了多少次 Binder 事务。

但这些只是可能的采集目标。单纯捕获 UIAutomator2 进程的 Binder 调用,并不等于捕获整个系统的全部 IPC,也不一定能完整恢复一次操作的所有间接调用链。

7. 如何在 Android 设备上初步观察 Binder?

可以通过 ADB 查看部分系统服务:

adb shell service list

可以列出系统已注册的 Binder 服务,例如 Activity、Window 等相关服务,具体名称取决于设备和版本。

也可以尝试:

adb shell dumpsys activity

查看 Activity 管理相关的系统状态。

这些命令有助于理解系统服务,但 不等于抓取了 Binder 通信报文。如果要追踪实际事务,还需要 Binder 相关的内核追踪能力、相应权限或调试环境。

最后总结

理解 Binder,可以记住三个层次:

应用层:AIDL / Proxy / Stub

定义接口,让跨进程调用像普通函数调用。

内核层:Binder Driver

在进程之间传递请求、引用和响应。

系统服务层:system_server 等

真正执行窗口管理、输入管理、无障碍交互等操作。

对于 UIAutomator2,最值得进一步研究的是:一次 click() 或 dump_hierarchy() 究竟触发了哪些 Binder 事务?

这能帮助你从调用系统 API 的层面,理解 UIAutomator2 是怎样操作 Android 系统的。