你真的了解AIDL吗? 附:AIDL 与 Binder 交互全解析

从「AIDL 服务端会不会被自动注册到 Binder 驱动」一路聊到「AMS 在 Binder 调用里到底起什么作用」的整理稿。


一、AIDL 服务端会被自动注册到 Binder 驱动 / ServiceManager 吗?

不会自动注册。 AIDL 只是个接口代码生成器 ------.aidl 文件编译后产出 Stub / Proxy 两个类,仅此而已。它不碰 Binder 驱动,也不碰 ServiceManager。

是否注册、注册到哪里,取决于你怎么用这个 Stub:

用法 是否在 binder 驱动注册 是否在 ServiceManager 注册 触发点
new IFoo.Stub() 当作普通对象返回 会(首次跨进程传递时) 否 第一次被 writeStrongBinder() 序列化跨进程
Service.onBind() 返回给 bindService() 调用方 会 否 AMS 把 binder 交给 client 时
ServiceManager.addService("foo", stub)(系统进程才能调) 会 会 显式 addService
Binder.allowBlocking() 等纯本地用 否 否 永远不跨进程

底层时序大致是这样:

scss 复制代码
new IFoo.Stub()
   ↓ (JavaBBinder native 对象延迟创建)
首次 writeStrongBinder(stub) 跨进程
   ↓ libbinder 通过 ioctl(BINDER_WRITE_READ)
   ↓ 把 BBinder 指针交给驱动
Binder Driver 创建对应 binder_node
   ↓ 在驱动内分配 handle 给接收方
接收方进程拿到 handle → 包装成 BinderProxy

关键点:

  1. binder 驱动里的「注册」是懒注册 ------直到这个 Binder 对象第一次跨进程传递,驱动里才有它的 binder_node。光 new Stub() 不传出去,驱动完全不知道它存在。
  2. ServiceManager 是额外一层命名服务 ,只给「全局可寻址」的服务用(getSystemService 那一类)。普通 App 的 AIDL 服务不会 也没权限 往里塞------addService 在普通进程会被 SELinux 拒绝,只有 system_server 等白名单进程能调。
  3. 普通 App 间通过 bindService 共享 AIDL:AMS 充当中介,把 server 进程的 Stub binder 通过 ServiceConnection.onServiceConnected 交给 client,绕过 ServiceManager。

一句话:AIDL 只生成代码,binder_node 由驱动按需创建,ServiceManager 名字只有系统服务才会注册。


二、AIDL 服务端 / 客户端通过 Binder 交互的全过程

核心矛盾:Client 拿到的只是一个整数 handle ,怎么就能精准调到另一个进程里的某个对象?答案分两步------先建立寻址关系 ,再走调用流程。

2.1 Binder 怎么"找到"服务端:寻址的建立

Binder 驱动里维护两张关键表:

  • binder_node:每个 Server 端的 Stub 在驱动里有唯一一个 node(裸指针级别的全局身份)
  • binder_ref:每个 Client 进程对某个 node 的「引用」,对应一个进程内的 handle 数字

handle 是进程私有的整数 ,类似 fd。同一个 Server,A 进程看到 handle=5,B 进程看到可能是 handle=12。驱动负责做 (进程, handle) → binder_node 的映射。

2.1.1 系统服务(如 AMS):通过 ServiceManager 寻址

sequenceDiagram participant SS as system_server participant K as Binder Driver participant SM as ServiceManager participant App as App Note over SS,SM: 启动期:注册 SS->>K: addService(&#34;activity&#34;, AMS_stub) K->>K: 为 AMS_stub 建 binder_node K->>SM: 在 ServiceManager 里登记<br/>(&#34;activity&#34; → node) Note over App,SM: 运行期:查找 App->>K: getService(&#34;activity&#34;) K->>SM: 查名字 SM-->>K: 返回 node K->>K: 在 App 进程建 binder_ref<br/>分配 handle (例如 17) K-->>App: 返回 handle=17 Note over App: 包装成 BinderProxy<br/>再包成 IActivityManager.Proxy

ServiceManager 自己也是个 Binder 服务,它的 handle 写死为 0------这是整个系统唯一不需要查找就能直达的入口。所有 Binder 寻址的起点都是它。

2.1.2 App 间 AIDL(bindService):通过 AMS 当中介

App 之间没有权限往 ServiceManager 注册,所以走 AMS 转交:

sequenceDiagram participant C as Client App participant AMS as AMS<br/>(system_server) participant K as Binder Driver participant S as Server App C->>AMS: bindService(Intent, conn) AMS->>S: 启动 Server 进程<br/>调 onCreate / onBind S-->>AMS: onBind() 返回 IFoo.Stub Note over S,AMS: 这次返回本身就是一次跨进程<br/>Stub 第一次经过 writeStrongBinder AMS->>K: 把这个 Binder 转交给 Client K->>K: 在 Client 进程建 binder_ref<br/>分配 handle AMS-->>C: ServiceConnection.onServiceConnected(IBinder) C->>C: IFoo.Stub.asInterface(binder)<br/>得到 IFoo.Proxy

注意:Stub 第一次跨进程传递时 ,驱动才给它创建 binder_node------之前一直只是个 Java 对象,驱动并不知道它存在。

2.2 一次方法调用的完整链路

假设 Client 调 iFoo.doSomething("hi"):

sequenceDiagram participant C as Client 线程 participant CP as IFoo.Proxy<br/>(Client 进程) participant CB as libbinder<br/>(Client) participant K as Binder Driver participant SB as libbinder<br/>(Server) participant ST as IFoo.Stub<br/>(Server 进程) C->>CP: doSomething(&#34;hi&#34;) CP->>CP: Parcel.writeInterfaceToken<br/>writeString(&#34;hi&#34;) CP->>CB: mRemote.transact(TRANSACTION_doSomething, data, reply, 0) CB->>K: ioctl(BINDER_WRITE_READ)<br/>BC_TRANSACTION + handle + parcel Note over C: Client 线程进入 S 状态<br/>(TASK_INTERRUPTIBLE) 等 reply K->>K: handle → binder_ref → binder_node<br/>找到目标进程 K->>K: 一次拷贝:数据 → Server mmap 区 K->>SB: 唤醒 Server 一个空闲 Binder 线程<br/>投递 BR_TRANSACTION SB->>ST: onTransact(code, data, reply, flags) ST->>ST: switch(code) → doSomething(data.readString()) ST-->>SB: 写 reply Parcel SB->>K: ioctl 写 BC_REPLY K->>K: 数据拷给 Client mmap 区<br/>唤醒原 Client 线程 K-->>CB: BR_REPLY CB-->>CP: transact() 返回,reply 已填好 CP-->>C: reply.readXxx() 返回结果

几个关键细节:

  1. handle 是寻址唯一依据 :Proxy 里持有的 BinderProxy 内部就是这个整数 handle。驱动收到 BC_TRANSACTION 时根据 (发起进程, handle) 查到 binder_node,再找到目标进程的 todo 队列。
  2. code 是路由方法 :AIDL 编译时给每个方法生成 TRANSACTION_xxx = IBinder.FIRST_CALL_TRANSACTION + n,onTransact 里就是一个 switch 分发。
  3. 一次拷贝 :Server 的接收 buffer 通过 mmap 映射到内核,Client 的 Parcel 数据直接从 Client 用户态拷到这块共享内存里,Server 用户态立即可读。整条链路只 1 次拷贝。
  4. 线程模型 :Server 端有 Binder 线程池(默认上限 15,按需 spawn)。Client 调用是同步阻塞(除非 oneway),Client 线程在内核里 S 状态睡觉等 reply。
  5. Token 校验 :writeInterfaceToken / enforceInterface 防止 handle 被错误调用------Client 写「我要调的是 IFoo」,Server 收到后必须匹配,否则抛 SecurityException。

2.3 把两步串起来:从字符串名字到一次远程调用

flowchart LR A[Client 发起 bindService] --> B[AMS 启动 Server 获得 Stub] B --> C[驱动建 binder_node 并分配 handle] C --> D[Client 拿到 BinderProxy] D --> E[调用 transact] E --> F[驱动按 handle 路由到 Server 线程池] F --> G[Stub.onTransact 按 code 分发] G --> H[reply 原路返回 Client]

一句话总结:

handle 是"地址",code 是"方法编号",Parcel 是"参数序列化",驱动负责按 handle 路由 + 一次拷贝唤醒目标线程池,ServiceManager / AMS 只是帮你"第一次拿到 handle"的中介。


三、Binder 驱动内部数据结构:binder_proc / binder_node / binder_ref

3.1 几个核心结构体

c 复制代码
// drivers/android/binder.c (简化)
struct binder_proc {
    struct rb_root threads;        // 本进程的 binder_thread,pid 为 key
    struct rb_root nodes;          // 本进程"提供"的 binder_node,ptr 为 key
    struct rb_root refs_by_desc;   // 本进程持有的 binder_ref,handle 为 key
    struct rb_root refs_by_node;   // 本进程持有的 binder_ref,node 指针为 key
    struct list_head todo;         // 待处理事务队列
    ...
};

struct binder_node {
    struct binder_proc *proc;      // 反向指针:这个 node 属于哪个 Server 进程
    binder_uintptr_t ptr;          // 用户态 BBinder 的指针(身份证)
    ...
};

struct binder_ref {
    struct binder_proc *proc;      // 反向指针:这个 ref 在哪个 Client 进程
    struct binder_node *node;      // 指向目标 node
    uint32_t desc;                 // 进程私有 handle 数字(就是 Client 看到的 handle)
    ...
};

对应关系:

概念 结构体 作用域 数量
进程 binder_proc 每打开一次 /dev/binder 一个 每进程 1
Server 端 Stub binder_node 全局唯一身份 每个 Stub 1
Client 端引用 binder_ref 进程私有 handle 每 (Client, Server) 1
Binder 线程 binder_thread 进程内 线程池 N

3.2 每个 binder_proc 维护四棵红黑树

flowchart LR subgraph SP[Server 进程 binder_proc] SN[nodes 红黑树 key=BBinder ptr] end subgraph CP[Client 进程 binder_proc] CRD[refs_by_desc 红黑树 key=handle] CRN[refs_by_node 红黑树 key=node 指针] end CRD -->|ref 指向 node| SN CRN -->|ref 指向 node| SN
  • refs_by_desc:Client 端用 handle 反查 ref------这是最常走的路径
  • refs_by_node:Client 端给定 node 查是否已有 ref,避免重复创建
  • nodes:Server 端用 BBinder 指针查 node,避免重复创建

3.3 一次 transact 的真实查找链路

Client 调 transact(handle=5, ...) 时驱动内部:

sequenceDiagram participant C as Client binder_proc participant R as binder_ref<br/>(handle=5) participant N as binder_node participant S as Server binder_proc participant T as Server binder_thread Note over C: ioctl 进入驱动<br/>当前 task → 找到 binder_proc C->>C: rb_search(refs_by_desc, key=5) C-->>R: 找到 binder_ref R->>N: ref->node N->>S: node->proc (找到目标 Server 进程) S->>T: 从 threads 树挑空闲线程<br/>或丢进 proc->todo T->>T: 数据拷到 Server mmap 区<br/>唤醒线程

所以更准确的描述是:

Client 给一个 handle → 在自己 的 binder_proc.refs_by_desc 红黑树里查 binder_ref → ref->node 拿到 binder_node → node->proc 拿到 Server 的 binder_proc → 把事务挂到 Server 的 proc->todo 或某个 binder_thread->todo,并唤醒对应线程。

3.4 常见用词修正

误区说法 准确说法
"binder_proc 在驱动红黑树以 node 作为 key" 应该是 "binder_ref 在 Client 的 refs_by_desc 树里以 handle 为 key;refs_by_node 才以 node 指针为 key"
"binder 驱动根据 node 找到服务端" node 本身就属于 Server 进程(node->proc 直接拿到),不需要"找",是 ref → node 这步起了"跨进程寻址"作用

四、用户态 / 内核态分工:这些事到底在哪发生?

整个 transact 流程横跨用户态和内核态,容易混淆。下面这张图把边界划清楚:

sequenceDiagram participant CU as Client 用户态<br/>(libbinder) participant K as Binder Driver<br/>(内核态) participant SU as Server 用户态<br/>(libbinder + Stub) CU->>K: ioctl(fd, BINDER_WRITE_READ, cmd=BC_TRANSACTION,<br/>handle=5, data_ptr, data_size) rect rgb(240, 240, 200) Note over K: ===== 以下全部在驱动里发生 ===== K->>K: 1) current → binder_proc (Client) K->>K: 2) rb_search(refs_by_desc, key=5) → binder_ref K->>K: 3) ref->node → binder_node K->>K: 4) node->proc → Server binder_proc K->>K: 5) copy_from_user 一次拷贝<br/>Client 数据 → Server mmap 区 K->>K: 6) 挑空闲 binder_thread 或挂 proc->todo K->>K: 7) wake_up 目标线程 end K-->>SU: 目标线程从 ioctl(BR_TRANSACTION) 返回<br/>data_ptr 直接指向 mmap 区 SU->>SU: BBinder::transact → onTransact (用户态分发) SU->>K: ioctl(BC_REPLY, reply 数据) rect rgb(240, 240, 200) Note over K: 同样在驱动里:reply 拷给 Client mmap 区<br/>唤醒等待的 Client 线程 end K-->>CU: ioctl(BR_REPLY) 返回

用户态 / 内核态分工:

阶段 在哪 谁做
Proxy.transact → Parcel 打包 用户态 libbinder (IPCThreadState)
ioctl(BINDER_WRITE_READ) 用户 → 内核切换 系统调用
handle → ref → node → proc 查找 内核态 binder.c
一次拷贝 + 唤醒目标线程 内核态 binder.c (binder_transaction())
onTransact 分发到具体方法 用户态 Stub.onTransact (你的 AIDL 代码)
业务方法执行 用户态 你的实现
reply 回写 内核态 binder.c

关键点:

  1. 寻址全部在内核 :binder_proc / binder_node / binder_ref 这些结构体和那四棵红黑树都是 drivers/android/binder.c 里的内核数据结构,用户态根本看不到、也碰不到。
  2. 用户态只知道 handle 这一个数字 :libbinder 拿这个数字塞进 binder_transaction_data 然后 ioctl 进内核,剩下「handle 怎么映射到目标进程」全是驱动的事。
  3. 方法分发回到用户态 :驱动只负责「把这包数据送到对面进程的某个线程的用户态 buffer」,至于 code 怎么解析、调哪个方法,那是 Server 用户态的 Stub.onTransact 干的。

邮政系统类比:

  • libbinder = 你贴邮票封信
  • Binder Driver = 邮局(拿门牌号查地址、跨城投递、唤醒收件人)
  • Stub.onTransact = 收件人拆信、分发到具体处理人

驱动做的就是中间那段「地址解析 + 物理投递」,绝不参与「信里写了啥」。


五、AMS 在 Binder 调用里到底起什么作用?

把整条链路按时间切两段就清楚了:

5.1 阶段一:建立连接(AMS 是中介)

只在 bindService / startService / startActivity 这种首次跨进程握手时,AMS 才参与:

sequenceDiagram participant C as Client App participant AMS as AMS (system_server) participant K as Binder Driver participant S as Server App Note over C,AMS: 1. Client 找 AMS 牵线 C->>K: transact 到 AMS (handle=固定的<br/>通过 ServiceManager 拿到) K->>AMS: bindService(Intent, conn) Note over AMS,S: 2. AMS 启动 Server 进程并要 Binder AMS->>S: 通过 Zygote fork + bindApplication<br/>调用 Service.onBind() S-->>AMS: onBind 返回 IFoo.Stub<br/>(跨进程时驱动建 binder_node) Note over AMS,C: 3. AMS 把 Binder 转交给 Client AMS->>K: writeStrongBinder(stub) 给 Client K->>K: 在 Client 进程建 binder_ref<br/>分配 handle K-->>C: onServiceConnected(IBinder) Note over C,S: 4. 牵线完成,AMS 退场

5.2 阶段二:之后的每一次方法调用(AMS 完全不在场)

一旦 Client 拿到了那个 IBinder(也就是拿到了 handle),后面 C → Binder Driver → S 直接走,根本不会经过 AMS:

sequenceDiagram participant C as Client App participant K as Binder Driver participant S as Server App Note over C,S: AMS 不在这张图里! C->>K: iFoo.doSomething(&#34;hi&#34;)<br/>ioctl(handle, BC_TRANSACTION) K->>K: handle → ref → node → Server proc K->>S: BR_TRANSACTION S->>S: Stub.onTransact → 业务方法 S->>K: BC_REPLY K-->>C: BR_REPLY

5.3 AMS 角色一览

场景 AMS 参与吗 它做什么
bindService 第一次连上 ✅ 启动 Server 进程 + 转交 IBinder
启动 Activity / Service ✅ 调度、生命周期、Zygote fork
系统服务(AMS/WMS/PMS)的调用 通过 ServiceManager 拿 handle 之后就直连,不再过 ServiceManager
拿到 IBinder 后调 iFoo.xxx() ❌ 完全不参与
拿到 IBinder 后调 linkToDeath ❌ 驱动直接处理
Server 死了通知 Client ❌ Binder 驱动发 BR_DEAD_BINDER

5.4 类比

  • ServiceManager = 系统服务的电话簿(只给系统服务用)
  • AMS = 婚介所,帮普通 App 牵线认识 Server
  • Binder Driver = 电话运营商

牵线完了你们打电话(transact)走的是运营商,婚介所没事。

这也是为什么 AMS 卡死不会立刻让所有 App 间通信都挂掉 ,但新的 bindService / startActivity 一定会卡。


六、一图总结

flowchart TB subgraph U1[Client 用户态] CA[业务代码 iFoo.doSomething] CP[IFoo.Proxy AIDL 生成] CL[libbinder IPCThreadState] end subgraph KN[Binder Driver 内核态] BR[binder_ref refs_by_desc key=handle] BN[binder_node node 指向 proc] BP[Server binder_proc todo 队列] BT[binder_thread 线程池] end subgraph U2[Server 用户态] SL[libbinder] SS[IFoo.Stub onTransact 分发] SI[业务实现] end SM[ServiceManager handle=0 只给系统服务] AMS[AMS 只在 bindService 时牵线] CA --> CP CP --> CL CL -->|ioctl BC_TRANSACTION| BR BR --> BN BN --> BP BP --> BT BT -->|BR_TRANSACTION| SL SL --> SS SS --> SI SI -->|reply| SL SL -->|BC_REPLY| BT BT -->|唤醒| CL CL -->|返回| CP CP -->|返回| CA SM -.连接.- BN AMS -.连接.- BN

记忆口诀:

  1. AIDL 只是个代码生成器,不动驱动也不动 ServiceManager
  2. binder_node 是 Server 的身份证,binder_ref 是 Client 持有的「门票」,handle 是门票编号
  3. 寻址全在内核,用户态只知道 handle 这一个数
  4. ServiceManager 只服务系统级服务,handle 写死为 0
  5. AMS 只在首次牵线时出场,之后的 transact 直连不经过它
  6. Binder Driver 是运营商,不关心信里写了啥,只负责按地址投递
相关推荐
千里马学框架4 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台4 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone4 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc4 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo4 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077004 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼4 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone4 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen4 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone4 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui