Binder 、 AIDL 、 IPC 关系

429 阅读7分钟

总览:一句话抓住关系

  • Binder = Android 的底层 IPC 机制(驱动 + 用户态库 + 服务注册中心)。
  • AIDL = 帮你生成基于 Binder 的 Stub/Proxy 代码的接口定义工具(语法糖)。
  • IPC = 跨进程通信的总称;Android 的主力方案几乎都“落地在 Binder 上”(AIDL、Messenger、Provider、Broadcast、System Service…)。

1) Binder 深入原理(内核 & 数据面)

1.1 关键组成

  • Binder 驱动/dev/binder,内核模块):管理传输、对象引用、线程唤醒、缓冲区。
  • libbinder(用户态) :提供 IBinder/Parcel 等抽象(Java 层和 Native 层各自有实现)。
  • ServiceManager(上下文管理器) :系统服务注册/查询中心(addService/getService)。
  • BBinder/BpBinder(C++) | Binder/BinderProxy(Java) :服务端“实体对象”/客户端“代理对象”。

1.2 内核关键结构(常被问)

  • binder_proc:代表一个进程在驱动中的上下文(包含其映射区、线程池等)。
  • binder_thread:代表进入驱动的每个线程(轮询、收发事务)。
  • binder_node:服务端导出的 Binder 实体(可被他进程引用)。
  • binder_ref:客户端对远端实体的“引用句柄”(handle)。
  • binder_transaction:一次请求/应答的封装。
  • binder_buffer:目标进程的映射区里分配的收发缓冲。

记忆法:proc/thread 管理执行,node/ref 管理对象引用,transaction/buffer 管理数据。

1.3 “一次拷贝”是怎么做到的

  • 发送方在用户空间准备好 Parcel 数据;
  • 驱动为接收方进程在它的 mmap 区域里分配 binder_buffer
  • 驱动把数据从发送方用户态直接拷到接收方的映射缓冲(一次 copy);
  • 接收方用户态直接“看到”这块缓冲里的数据(因为已映射到它的地址空间)。

对比传统 IPC(如 Socket):用户→内核 + 内核→用户 = 两次
Binder:用户→“对端的映射缓冲”(内核完成一次 copy)= 一次

1.4 事务时序(含命令流)

  1. Client transact() → 进入驱动(ioctl(BINDER_WRITE_READ))→ 发送 BC_TRANSACTION
  2. 驱动分配目标进程 buffer,复制数据,入队目标 binder_thread
  3. Server 线程池被唤醒,读到 BR_TRANSACTION,执行 Stub onTransact()
  4. 执行完成→ 写回 BC_REPLY,客户端收到 BR_REPLY
  5. 同步调用在 Client 线程阻塞等待,异步(FLAG_ONEWAY)不等待。

关键点:Java 层你只看见 transact/onTransact;内核里其实是 BC_* / BR_* 命令来回跑。

1.5 线程池与并发

  • 每进程一个 Binder 线程池(上限常见 16,可调),由驱动按需唤醒。
  • onTransact()Binder 线程里执行(不是主线程)。
    切忌onTransact() 里做耗时(I/O/网络/锁),否则“卡死”池子。

1.6 安全 & 鉴权

  • 驱动在 binder_transaction_data附带调用方 UID/PID;服务端可用
    Binder.getCallingUid()/getCallingPid() 校验权限/签名。
  • 再叠加 Android 权限系统 + SELinux(system_server 与 app 间)。

1.7 传递大对象与 FD

  • Bundle 太大 → 触发 TransactionTooLargeException(常见 ANR 根因)。
    规则:每进程 Binder 事务缓冲区约 1MB(含并发事务占用,非“每次都能 1MB”)。
  • 大块数据:用 ashmem/共享内存,通过 Binder 仅传 文件描述符(fd)BINDER_TYPE_FD)。

1.8 进程死亡与重连

  • linkToDeath(DeathRecipient) 监听对端进程死亡(如服务崩溃),回调 binderDied() 做重连/降级。

2) AIDL:生成了什么代码,怎么跑

2.1 编译产物(Java)

给定:

aidl
复制编辑
interface IBook {
  String name();
  oneway void prefetch(int id);
}

编译生成 IBook.java,里面含:

  • public interface IBook extends IInterface

  • public static abstract class Stub extends Binder implements IBook

    • onTransact(int code, Parcel data, Parcel reply, int flags):根据 code 分发方法。
    • public static IBook asInterface(IBinder obj):把 IBinder 包装成 Proxy 或返回本地实现。
  • private static class Proxy implements IBook

    • name():打包到 ParcelmRemote.transact(code, data, reply, 0)
    • prefetch()transact(..., FLAG_ONEWAY) 异步无返回

方法 ID(transaction code) 是编译期为每个接口方法分配的常量。
onewayFLAG_ONEWAY,调用端不阻塞、服务端入队“异步队列”。

2.2 类型系统 & 方向限定

  • 支持:基本类型、String/CharSequenceParcelableList/Map<支持类型>IBinderFileDescriptor
  • in/out/inout:决定数据是单向输入单向输出还是双向(影响序列化与 copy 行为)。
  • Parcelable > Serializable:避免反射 & 临时对象,性能高。

2.3 版本演进与兼容

  • 新增字段:Parcelable 末尾新增字段 → 旧端反序列化时默认忽略(要注意读写顺序一致)。
  • 新增方法:最好新增接口版本号feature flag(服务端可暴露 getVersion(),客户端按版本分支)。
  • AIDL “稳定接口”/NDK AIDL(HAL 层)有更严格的 versioning 机制(VINTF/稳定 AIDL),App 层常用 Java AIDL 可用“软约定”做兼容。

3) 系统里哪里用了 Binder(有感知就能“举例 50 个”)

  • 系统服务(全部 Binder)AMS/WMS/PMS/IMMS/Location/Audio/Power/Camera/Sensor/Clipboard/Notification…

  • 四大组件

    • Activity/Service 的调度都要和 system_server 讲(AMS)。
    • ContentProviderquery/insert/update/delete 都是远程过程调用。
    • 广播分发底层也走 Binder。
  • App 层典型MessengerbindService + AIDLMediaPlayer/MediaSessionClipboardManagerJobSchedulerAlarmManager


4) IPC 全家桶:怎么选

方案底层优点适用
AIDL/BinderBinder高性能、强类型、同步/异步复杂接口、系统/业务服务
MessengerBinder简单、Message 队列轻量单请求/回执
ContentProviderBinder权限细粒度、URI 共享跨进程数据访问
BroadcastBinder一对多通知全局事件
Socket/WebSocketTCP/UDP跨设备/网络IM/长连接
共享内存 (ashmem/mmap)内核超大数据、低 copy媒体/图像/模型
文件/DB文件系统简单、持久非实时共享

大对象 → 共享内存 + 传 fd高频小 RPC → AIDL一次多端通知 → Broadcast纯数据共享 → Provider


5) 工程实践:常见坑 & 最佳实践

  1. 避免大 Bundle:跨进程传大图/大数组 → TransactionTooLargeException;改用共享内存/文件 + fd。
  2. 不要阻塞 Binder 线程onTransact() 里甩到业务线程/线程池,快速返回。
  3. 连接健壮性linkToDeath + 自动重连;拿不到服务先 getService()checkPermission()
  4. 权限校验:服务端统一做 UID/PID 检查、enforcePermission()/签名权限/SELinux domain。
  5. oneway 滥用:异步会在目标进程排队,过多会挤爆队列;对时序敏感的要同步。
  6. 接口演进:加 getVersion();新字段在尾部;旧端默认值;灰度双写。
  7. 线程池上限:默认 ~16;大量并发 onTransact() 可能饿死,必要时拆分服务或限流。
  8. fd 生命周期:跨进程传 fd 后要明确由谁负责关闭,避免 fd 泄漏。

6) 面试“追问链”快答卡

  • Q:Binder 为什么一次拷贝?
    A:驱动在接收方 mmap 区域分配 buffer,把数据从发送方 user 拷到这块映射内存,接收方直接可见 → 只需一次 copy。
  • Q:Binder 内核里对象怎么表示?
    A:服务端 binder_node,客户端 binder_ref;一次调用是 binder_transaction,数据放在 binder_buffer
  • Q:AIDL 生成了什么?
    A:IInterface 接口 + Stub(服务端,继承 BinderonTransact 分发)+ Proxy(客户端,transact 打包 Parcel)。
  • Q:为什么/何时用 oneway?
    A:通知类/不关心返回值场景;注意过多 oneway 会把对端队列挤爆,且无背压回馈。
  • Q:TransactionTooLargeException 为何发生?
    A:每进程 Binder 事务缓冲约 1MB,含并发占用;一次传大对象/大 Bundle 就会爆;建议改用共享内存 + fd。
  • Q:服务端如何鉴权?
    A:Binder.getCallingUid()/getCallingPid() + 权限(enforceCallingPermission)+ 签名校验 + SELinux。
  • Q:ServiceManager 做什么?
    A:系统服务注册/查询中心;addService/getService;system_server 启动时把 AMS/WMS/PMS 注册进去。

7) 小图记忆(ASCII)

css
复制编辑
Client(Proxy) --Parcel--> [Binder Driver] --Parcel--> Server(Stub)
            \            mmap/一次拷贝             /
             --------- UID/PID 安全校验 ---------/

8) 一句话套路收尾(面试总结)

Binder 是地基:一次拷贝、高安全、线程池、引用模型,系统服务全靠它;
AIDL 是工具:自动生成 Stub/Proxy,强类型 RPC;
IPC 选型看场景:小而频 → AIDL;一对多 → Broadcast;数据共享 → Provider;大数据 → 共享内存+fd;跨设备 → Socket。
工程关键:别阻塞 Binder 线程,别传大对象,权限要严,接口要可演进,故障要可恢复。