如何排查 Binder 问题
如何排查 Binder 问题
前六章把 Binder 从概念讲到生命周期。这一章把它们串成一条可执行的排障链:遇到 Binder 相关问题时,按什么顺序查、看什么证据、怎么区分原因。
Binder 故障表现在客户端,根因却可能位于服务注册、客户端调用线程、服务端线程池、事务 载荷或远端对象生命周期。只盯着一行异常日志,无法判断请求是否已经到达服务端。
先确定阻塞在哪一端
| 常见误解 | 正确理解 |
|---|---|
| Binder 问题看 logcat 就够了 | 日志只给线索,判断“卡在哪”需要栈、服务状态与事务证据 |
| 客户端调用慢 = 服务端慢 | 也可能是线程池占满、事务太大、引用失效或客户端线程被抢占 |
DeadObjectException 是业务异常 | 它通常表示远端对象已死,属于引用生命周期问题 |
排查时先确认服务是否存在,再对照客户端与服务端线程栈,最后检查事务大小、方向和接口 版本。这样可以把“没有目标”“送不进去”“无人处理”和“处理太慢”分开。
核心结论
- 排障顺序固定:服务状态 → 调用线程 → 事务证据 → 代码匹配,不要跳步。
- 常见异常有明确指向:
DeadObjectException→ 远端死亡;TransactionTooLargeException→ 事务超限;ANR 栈里的 Binder 等待 → 调用阻塞。 - 服务端线程池占满会让客户端“调用变慢”,但问题在服务端吞吐。
- 同进程与跨进程的失败表现不同,先确认调用确实跨了进程。
- 修复后要复现验证:杀掉服务、重启进程、反复触发,确认重连与异常路径。
四层检查链
逐步解释:
- 服务状态:先确认服务名是否注册、进程是否存活,排除“根本没服务”。
- 调用线程:看客户端线程栈,确认是否阻塞在 Binder 事务等待。
- 事务证据:看异常类型与事务大小,排除载荷问题。
- 代码匹配:确认两边接口版本一致、参数方向正确、code 未错位。
常用命令与证据
环境:开发设备;标注 root 的命令需要 adb root 或 su,user 版本不可用。
adb shell service list # 服务是否注册
adb shell dumpsys activity service <名称> # 服务状态
adb shell ps -A | grep <进程名> # 进程是否存活
adb shell kill -3 <pid> # 抓 Java 线程栈
adb shell su 0 cat /sys/kernel/debug/binder/transactions # root:内核事务
adb logcat -v threadtime | grep -E "Binder|TransactionTooLarge|DeadObject"| 证据 | 含义 | 下一步 |
|---|---|---|
service list 没有该服务 | 服务未注册或已退出 | 查服务启动日志与崩溃原因 |
线程栈卡在 BinderProxy.transact | 客户端阻塞等待 reply | 查服务端线程池与业务耗时 |
TransactionTooLargeException | 事务超过约 1MB | 改传 fd、分页或缩小参数 |
DeadObjectException | 远端对象已死 | 检查服务存活与重连逻辑 |
| ANR trace 出现 binder 等待 | 主线程被慢服务拖住 | 移出主线程或优化服务端 |
| 服务端 binder 线程数接近上限 | 线程池占满 | 优化服务端吞吐,不盲目加线程 |
常见场景与定位思路
场景一:主线程调用慢服务导致 ANR
栈特征:主线程停在 BinderProxy.transact() 的等待处。先看服务端对应线程是否在执行 耗时逻辑,再看是不是每次调用都慢。修复方向:调用移出主线程,或优化服务端。
场景二:大数据事务崩溃或变慢
特征:日志出现 TransactionTooLargeException,或调用耗时与参数大小成正比。先量化参数 大小,再改用文件路径、传 fd,或分页查询。
场景三:服务重启后调用失败
特征:先是 DeadObjectException,重试后恢复或一直失败。检查 binderDied() 是否清理了 缓存、重连是否带退避,避免服务未就绪时无限重试。
场景四:服务端线程池占满
特征:客户端调用排队变慢,服务端 ps -T 里 binder 线程数接近上限。检查是否有慢调用 长期占住线程,而不是直接调大线程池上限。
常见误区
- “日志里没异常就是没问题”:Binder 问题可能只表现为慢或卡,没有异常日志。
- “客户端慢就优化客户端”:先确认服务端线程是否空闲,可能瓶颈在服务端。
- “
oneway后所有异常都收不到”:它不等待 reply,业务异常不回传,但发送阶段问题仍 可能暴露。 - “直接调大线程池就能解决慢”:慢的根本原因不解决,只是把排队问题推迟。
- “同进程调用也查 Binder”:同进程走本地路径,先确认调用真的跨了进程。
延伸问题
- 如何用 Perfetto 抓取 Binder 事务的时间线?
binder_transactions内核节点与用户态日志如何对应起来?- 系统服务常用的“重试 + 监听注册”框架是怎样的?
- 如何设计一个可测试的 Binder 服务,让异常路径可以被稳定复现?
源码入口
| 文件 | 关键位置 | 作用 |
|---|---|---|
BinderProxy.java | transact() | 客户端阻塞等待的栈位置 |
Binder.java | onTransact() | 服务端分发入口,打日志观察 |
IPCThreadState.cpp | waitForResponse() | Native 等待 reply |
binder.c | binder_thread_read() | 内核事务读取与唤醒 |
公共路径与前面各章一致:Java 侧 frameworks/base/core/java/android/os/,Native 侧 frameworks/native/libs/binder/,内核侧 drivers/android/。行号以 r75 检索为准。
需要重新核对完整角色与链路时,回到 Binder 通信。