Binder 不等于 AIDL:一次跨进程调用经过了什么
Binder 不等于 AIDL:一次跨进程调用经过了什么
先看一行很普通的代码。它在应用进程里执行,拿到的窗口管理服务实现却在 system_server 里:
// 节选:frameworks/base/core/java/android/view/WindowManagerGlobal.java(android-14.0.0_r75)
sWindowManagerService = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"));这里有两件事值得拆开看。ServiceManager.getService("window") 返回的是一个 IBinder,它不是窗口服务本身,而是指向另一个进程的引用。IWindowManager.Stub.asInterface() 把这个引用包装成接口,之后调用 addView() 之类的方法,写法和本地调用完全一样。中间隔着两个进程,把它们连起来的就是 Binder。
这篇文章想回答的问题是:这个引用背后,一次调用究竟经过了哪些对象、哪些线程,失败的时候又会停在哪一步。
先把三个名字分开
很多人会把 Binder、AIDL、ServiceManager 当成同一样东西,原因也好理解:写 AIDL 的时候,三者几乎同时出现。但它们负责的事情不一样,分开看会清楚很多。
AIDL 是代码生成工具。它读取 .aidl 文件,生成 Stub 和 Proxy。Proxy 把参数写进 Parcel,Stub 从 Parcel 里读出参数再调用实现。AIDL 本身不传输任何数据,也不决定哪个进程执行方法。
ServiceManager 是名字服务,只做一件事:按名字找到一个 Binder 引用,比如按 "window" 找到窗口服务。找到以后,后续的调用就和它没有关系了。
Binder 才是传输和调用的机制。它由应用侧的 BinderProxy、native 层的 IPCThreadState 和 ProcessState,以及内核里的 Binder 驱动组成。AIDL 生成的代码,最终都要落到这套机制上。
我更愿意这样记:AIDL 管“接口怎么写”,ServiceManager 管“找谁”,Binder 管“调用怎么送过去,结果怎么送回来”。
跟着一次调用走
下面按调用的先后顺序走一遍。每一步都能在 android-14.0.0_r75 中找到对应的符号,文件和方法名我都写了出来,方便你自己对照。

先看客户端。远程对象的 transact() 定义在 frameworks/base/core/java/android/os/BinderProxy.java,它通过 JNI 进入 native 层,入口是 frameworks/base/core/jni/android_util_Binder.cpp 中的 android_os_BinderProxy_transact。这一层做的事很朴素:把参数交给 native,由 IPCThreadState::transact() 接手。
transact() 第一件事是把事务写进命令缓冲区,命令是 BC_TRANSACTION(frameworks/native/libs/binder/IPCThreadState.cpp)。BC_ 开头的命令表示用户态发给驱动的指令。真正把命令交给内核的是 talkWithDriver(),核心只有一行:
// 节选:frameworks/native/libs/binder/IPCThreadState.cpp(talkWithDriver)
if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)这一行 ioctl 是用户态进入内核的入口,它同时完成两件事:把待发送的命令交给驱动,并把驱动返回的命令读回来。之后调用线程在 waitForResponse() 里等待,直到读到 BR_REPLY。
客户端的工作到这里就结束了。事务进入内核后,驱动负责找到目标进程,把事务投递给 system_server 里的一个 Binder 线程。这份 AOSP 树里只有用户态代码,驱动的实现不在其中,投递的内部细节我会在后面单独说明。
服务端怎么接住请求
服务端收到的命令是 BR_TRANSACTION,由 IPCThreadState::executeCommand() 处理。这里有一个容易忽略的细节:处理分支会从事务结构里取出调用方身份,也就是 mCallingPid = tr.sender_pid 和 mCallingUid = tr.sender_euid。Binder.getCallingUid() 能返回正确的 UID,靠的正是这里的赋值。身份是在 native 层分发事务时确定的,业务代码并不需要自己去查。
事务接着进入 BBinder::transact()(frameworks/native/libs/binder/Binder.cpp)。Java 服务对象在 native 侧的实例是 JavaBBinder,它通过 JNI 调用 Binder.execTransact(),后者再进入 onTransact()(frameworks/base/core/java/android/os/Binder.java)。AIDL 生成的 Stub 重写了 onTransact():根据 code 决定调用哪个接口方法,从 data 里读出参数,调用服务实现,最后把返回值写进 reply。
所以参数是在 Stub 读取 data 的那一刻被拆出来的,服务实现也是在那里执行的。如果你想知道某个参数在服务端什么时候被读出来,答案就在 onTransact 的那几行里。
那么服务端的线程从哪里来?ProcessState::startThreadPool()(frameworks/native/libs/binder/ProcessState.cpp)负责启动线程池,线程由 spawnPooledThread() 创建,上限是 DEFAULT_MAX_BINDER_THREADS,值为 15。
这个数字对排查很重要。线程池里的线程如果都阻塞在某个慢调用中,新的请求只能排队,调用方看到的往往是超时,服务本身看上去却并没有崩溃。我的判断是:遇到“调用某个系统服务方法超时”,应该先看服务端的 Binder 线程在做什么,而不是先怀疑驱动。这是排查顺序上的判断,具体还要结合线程栈确认。
servicemanager 是个例外。它在 main() 里把线程池上限设成了 0(frameworks/native/cmds/servicemanager/main.cpp),改用 setupPolling() 和 handlePolledCommands() 轮询 Binder 的文件描述符,所有查询都在一个线程里处理。实现很简单,但也意味着:只要有一个查询慢,其他服务的注册和查找都会受影响。
ServiceManager 怎么找到人
知道了服务端怎么接收请求,还剩一个问题:客户端怎么拿到 "window" 这个引用?
ServiceManager 的特殊之处在于它自己的地址是固定的。servicemanager 启动时调用 becomeContextManager()(main.cpp 第 149 行),把自己登记为上下文管理器,并通过 setTheContextObject() 设置上下文对象。之后,各进程里句柄为 0 的引用都指向它。ProcessState 按句柄取引用时,遇到 handle == 0 且上下文对象已设置,就直接返回它(ProcessState.cpp 第 318 行附近),这就是“固定地址”的来源。
Java 层的 ServiceManager.getService() 会先查本地缓存 sCache。这里很容易想当然地认为“查过一次就会缓存”,但源码并不是这样。sCache 只在系统启动阶段由 initServiceCache() 一次性填充。普通的 getService() 查不到缓存时,会走 rawGetService(),也就是对句柄 0 发起一次 transact(),再由 servicemanager 在 ServiceManager.cpp 的 getService() 中查表。

这意味着,每查找一个不在启动缓存里的服务,都要多走一次跨进程调用。如果应用里频繁调用 ServiceManager.getService(),这个开销是真实存在的。我的工程建议是自己持有查到的引用,而不是每次都查一遍。这是建议,不是源码的强制要求。
注册服务的路径与此对称:服务进程调用 addService,最终由 ServiceManager.cpp 的 addService() 把名字和引用写进表中。
为什么不直接用 socket 或共享内存
Linux 已经有管道、socket 和共享内存。Android 选 Binder,是因为它要同时满足三件事:数据不能每次都整段复制;调用方身份要可靠;调用语义要像“调用一个方法、拿到返回值”。Socket 能做到前两点的一部分,但第三点要自己在应用层实现;共享内存拷贝快,却没有调用语义。
用户态代码能直接看到的,是 Binder 的内存映射。ProcessState 打开驱动后会映射一块缓冲区,大小是 BINDER_VM_SIZE,即 1MB 减去两个页(ProcessState.cpp 第 49 行)。映射使用 PROT_READ 和 MAP_PRIVATE | MAP_NORESERVE,接收方通过只读映射读取事务数据。
常见的说法是“Binder 只需要一次拷贝”。这句话的完整实现在内核驱动里,而这份 AOSP 树里没有驱动源码,所以这篇文章只讲到用户态映射为止。要确认“一次拷贝”的具体过程,需要结合内核的 drivers/android/ 源码来看,这一部分我留给单独的内核篇。
对象死了怎么办
跨进程调用最麻烦的情况,是对方进程已经退出。Binder 用死亡通知处理这种情况。
应用侧调用 linkToDeath(recipient, flags) 注册回调。它在 Binder.java 中定义,经 BinderProxy 进入 JNI 的 android_os_BinderProxy_linkToDeath,再由 native 层的 IPCThreadState::requestDeathNotification() 发起登记。远端进程退出后,服务端的 executeCommand() 会处理 BR_DEAD_BINDER,最终回调到 DeathRecipient。
如果没有注册死亡通知,调用方只能在下一次调用失败时才发现对方已经不在了。JNI 层会把 DEAD_OBJECT 转换成 DeadObjectException,但它是受检异常,只在部分方法里抛出。所以我不建议依赖“调用一定会抛异常”。对于需要长期持有的引用,我的做法是注册死亡通知,在回调里清理状态,再决定要不要重新获取服务。
排查时先问哪一步
Binder 调用失败时,先分清两类问题:是找不到服务,还是找到了但调用失败。两者的排查方向完全不同。
如果怀疑服务没有注册,可以在目标设备上执行:
adb shell service list在列表里找 window。如果它不在,问题出在服务注册或进程启动阶段,还没有走到调用那一步。
如果服务已经找到、调用却失败,就看 logcat:
adb logcat -v threadtime | grep -E "TransactionTooLarge|DeadObject"DeadObjectException说明对方进程已经死亡,或者引用已经失效。先确认对方进程是否重启过。TransactionTooLargeException说明单次事务超过了缓冲区限制。异常类定义在frameworks/base/core/java/android/os/TransactionTooLargeException.java。
这两条命令在普通设备上一般可以执行,但权限和输出会因设备与系统版本而异。我没有在真机上逐条验证,使用时请以你手上设备的输出为准。
结尾
回到开头那行代码。ServiceManager.getService("window") 返回的是指向 system_server 窗口服务的引用。应用线程调用接口方法时,Proxy 把参数写进 Parcel,经 IPCThreadState 通过 BINDER_WRITE_READ 交给内核;内核把事务投递到 system_server 的 Binder 线程;线程在 executeCommand 中记下调用方身份,再经 JavaBBinder 和 execTransact 进入 AIDL 生成的 onTransact。参数在 Stub 读取 data 时被拆出来,实现方法也在那里执行。
读懂这条路径以后,排查 Binder 问题只需要先回答两个问题:这次调用的实现在哪个进程?参数是在哪一步被拆包的?能回答这两个问题,大多数 Binder 故障都能缩小到一个明确的范围。
源码入口
| 符号 | 位置(android-14.0.0_r75) | 作用 |
|---|---|---|
WindowManagerGlobal.getWindowManagerService() | frameworks/base/core/java/android/view/WindowManagerGlobal.java:201 | 应用侧获取 IWindowManager 的示例入口 |
ServiceManager.getService() | frameworks/base/core/java/android/os/ServiceManager.java:169 | Java 层按名查找,先查 sCache |
ServiceManager.initServiceCache() | frameworks/base/core/java/android/os/ServiceManager.java:408 附近 | 系统启动时一次性填充缓存 |
BinderProxy.transact() | frameworks/base/core/java/android/os/BinderProxy.java:528 | 客户端事务入口 |
android_os_BinderProxy_transact | frameworks/base/core/jni/android_util_Binder.cpp:1361 | JNI 桥接到 native |
IPCThreadState::transact() | frameworks/native/libs/binder/IPCThreadState.cpp:808 | 写入 BC_TRANSACTION |
IPCThreadState::talkWithDriver() | frameworks/native/libs/binder/IPCThreadState.cpp:1091 | 调用 BINDER_WRITE_READ |
IPCThreadState::executeCommand() | frameworks/native/libs/binder/IPCThreadState.cpp:1257 | 服务端处理 BR_TRANSACTION 与 BR_DEAD_BINDER |
BBinder::transact() | frameworks/native/libs/binder/Binder.cpp:359 | native 服务对象的分发入口 |
Binder.execTransact() / onTransact() | frameworks/base/core/java/android/os/Binder.java:1425、:1012 | Java 层分发与 AIDL Stub 入口 |
ProcessState 的映射与线程池 | frameworks/native/libs/binder/ProcessState.cpp:49、193、403、572 | 映射区与线程池 |
servicemanager 入口 | frameworks/native/cmds/servicemanager/main.cpp:126 | 上下文管理器注册与轮询 |
binder_transaction_data | bionic/libc/kernel/uapi/linux/android/binder.h:145 | 用户态可见的事务结构 |