主线程上出现一段长 Binder 调用,慢的可能是服务端处理,也可能是事务派发、服务端线程排队或锁等待。这篇从一次调用出发,用 Perfetto 的 Binder 事件、线程状态和 Java monitor contention,把等待时间逐段分开。
这些信号需要在抓 Trace 时启用;看到主线程 Sleeping 或一个 futex 栈,只能确定它在等待,不能直接写成「Binder 慢」或「Java 锁慢」。
本文目录
- Perfetto 系列文章
- Binder 基础与案例
- Perfetto 观测准备
- 其他 Binder 分析工具
- Binder 分析工作流
- 步骤一:定位事务耗时
- 步骤二:评估线程池与 Oneway 队列
- 步骤三:排查锁竞争
- 最新平台特性与优化建议
- 总结
- 参考
- 关于我 && 博客
Perfetto 系列文章
- Android Perfetto 系列目录
- Android Perfetto 系列 1:Perfetto 工具简介
- Android Perfetto 系列 2:Perfetto Trace 抓取
- Android Perfetto 系列 3:熟悉 Perfetto View
- Android Perfetto 系列 4:使用命令行在本地打开超大 Trace
- Android Perfetto 系列 5:Android App 基于 Choreographer 的渲染流程
- Android Perfetto 系列 6:为什么是 120Hz?高刷新率的优势与挑战
- Android Perfetto 系列 7:MainThread 和 RenderThread 解读
- Android Perfetto 系列 8:深入理解 Vsync 机制与性能分析
- Android Perfetto 系列 9:CPU 信息解读
- Android Perfetto 系列 10:Binder 调度与锁竞争
- Android Perfetto 系列 11:PerfettoSQL、Trace Processor 与回归检测
- Android Perfetto 系列 12:Trace 数据流与丢失排查
- Android Perfetto 系列 13:Perfetto SDK、Track Event 与 App 现场 Trace
- Android Perfetto 系列 14:heapprofd 与内存 Profiling
- Android Perfetto 系列 15:Boot Trace、长时间现场 Trace 与偶发问题抓取
- Android Perfetto 系列 16:GPU、Power Counters 与硬件瓶颈分析
- Android Perfetto 系列 17:专项场景自动化、问题清单与平台体系
- Android Perfetto 系列 18:响应速度实战,从输入事件到首帧反馈
- 视频(B站) - Android Perfetto 基础和案例分享
- 视频(B站) - Android Perfetto 分享 - 出图类型分享:AOSP、WebView、Flutter + OEM 系统优化分享
Binder 基础与案例
Binder 是 Android 常用的跨进程调用机制。一次同步调用至少要看发起线程、驱动里的事务和服务端处理线程;客户端等待返回的时间还可能包含服务端排队与调度。
- Client:应用线程通过
IBinder.transact()发起调用,将Parcel序列化的数据写入内核。 - Service(Server):通常运行在 SystemServer 或其他进程中,通过
Binder.onTransact()读取Parcel并执行业务逻辑。 - Binder Driver:Binder 驱动通过
/dev/binder设备节点提供进程间通信,负责事务派发、缓冲区管理、优先级继承等,是连接双方的“信使”。 - Thread Pool:服务端通常维护一组按需增长的 Binder 线程。普通进程的默认上限常见为 15 个工作线程,但
system_server和厂商配置可能不同;达到上限且线程都在处理事务时,新请求才需要排队等可用线程。
为什么需要 Binder?
Android 采用多进程架构来隔离应用、提升安全性与稳定性。每个 APK 运行在独立的用户空间,当需要访问系统能力(相机、位置、通知等)时,必须跨进程调用 Framework 或 SystemServer。
选择 Binder 时,更值得比较的是 Android 已经提供的身份、对象引用和线程池语义:
| IPC 方式 | 问题 |
|---|---|
| Socket | 能跨进程通信并获取对端身份,但接口、对象生命周期和调用约定需要应用自行设计 |
| Pipe | 适合字节流传输,不直接提供 Binder 的对象调用语义 |
| 共享内存 | 适合大块数据,仍需另行协调访问和生命周期 |
Binder 提供了三个关键能力:一是调用方身份(向服务端提供调用方 UID/PID,具体权限仍需服务端校验);二是同步与异步调用(同步模式下 Client 等待 Server 返回,这是最常见的模式,而异步模式下 Client 发送后不等待服务端执行完成,适用于通知、状态上报等场景);三是优先级继承(同步事务可在一定条件下继承调用方优先级,降低优先级反转风险,具体效果受调度策略约束)。
应用启动时的 IActivityManager#attachApplication() 就是一条 Binder 路径。看这条路径时,要把客户端等待与 system_server 里的处理分开计时。
从 App 开发者视角的案例
假设我们在 Trace 里关注到 AIDL::java::IActivityManager::attachApplication::server。它对应的是应用进程通过 IActivityManager#attachApplication(...) 发起的一次同步 Binder 调用,服务端实现位于 system_server 的 ActivityManagerService。调用路径可以概括为:先在 Proxy 侧,应用进程通过 ActivityManager.getService() 拿到一个 IActivityManager 的代理对象(BinderProxy);然后进行序列化,调用 attachApplication(...) 时,代理会把参数写入 Parcel,执行 transact();接着是内核传输,Binder 驱动将该事务排入 system_server 的 Binder 线程队列,并唤醒一个空闲线程(例如 Binder:1460_5);随后在 Stub 侧,ActivityManagerService(Stub)所在的线程被唤醒,读取参数并进入 attachApplication 的处理流程;最后是返回阶段,Service 处理完毕,将结果写入 Parcel,驱动唤醒原 App 线程,App 线程从 waitForResponse() 返回继续执行。
在 Perfetto 中,这条链路会显示为:Android Binder / Transactions 轨道上的一次事务(如果 trace 中能解析到 AIDL 信息,Slice 名称会类似 AIDL::java::IActivityManager::attachApplication::client/server,或在 SQL 中体现为 aidl_name=IActivityManager、method_name=attachApplication);App 线程在 thread_state 里处于 S (Sleeping) 状态(同步调用时常见),且 blocked_function 通常涉及 binder_thread_read / epoll_wait / ioctl(BINDER_WRITE_READ);SystemServer 的 Binder 线程出现 Running 切片;以及 Flow 箭头(Perfetto 会用箭头把 Client 的 transact 和 Server 的处理线程连接起来)。

Perfetto 观测准备
要在 Perfetto 中诊断 Binder,需要提前准备好数据源与 Trace 配置。
数据源与轨道总览
Binder 分析需要把「事务事件」和「线程调度/阻塞/锁」串起来。录制侧主要依赖 linux.ftrace(包含 Binder tracepoints、调度事件以及可选的 atrace 类别),再配合少量元数据源(进程/线程命名映射)。
linux.ftrace(内核层 + atrace) 记录可用的 Binder tracepoints 与调度事件。binder_transaction、binder_transaction_received 和 sched_waking 能帮助定位事务与线程切换;事件可用性和字段随内核、Android 版本变化。binder_transaction_alloc_buf 只能提供分配线索,不能单靠出现一次事件就诊断缓冲区耗尽。
另外,linux.ftrace 里还可以开启 atrace 类别来补充用户态 Slice:binder_driver/am/wm 等有助于解释系统服务语义;dalvik 则用于采集 ART 的 monitor contention(Java synchronized 竞争),从而在 UI 里出现 Thread / Lock contention 相关轨道。
linux.process_stats(元数据) 用于把 PID/TID 映射成进程名/线程名,方便在 UI 和 SQL 中阅读与过滤。开销极低,建议常开。
说明:Perfetto UI 中的 Android Binder / Transactions、Android Binder / Oneway Calls 轨道,以及 PerfettoSQL 标准库中的
android.binder/android.monitor_contention模块,都是在 trace processor 侧基于上述原始事件解析/聚合出来的,并不是需要额外开启的“录制数据源”。
Trace Config 推荐
以下配置兼顾了兼容性与新特性,建议作为标准的 Binder 分析模板。将配置保存为 binder_config.pbtx 即可使用:
1 | # ============================================================ |
配置项说明
| 数据源 | 作用 | 本系列适用范围 | 开销 |
|---|---|---|---|
linux.ftrace (binder/*) |
内核层 Binder 事件 | Android 12–17;具体事件以设备内核为准 | 低 |
linux.ftrace (sched/*) |
调度事件,串联线程唤醒 | Android 12–17;具体事件以设备内核为准 | 中 |
linux.ftrace (atrace: dalvik/…) |
Framework Slice + Java Monitor Contention | Android 12–17;字段随版本演进 | 低-中 |
linux.process_stats |
进程名称和 PID 映射 | Android 12–17 | 极低 |
提示:本文的 Binder 分析工作流只依赖
linux.ftrace(binder tracepoints + sched + dalvik),因此 Android 12–17 的抓取思路基本一致。不同版本的 UI 字段名可能略有差异,遇到差异时推荐用 Perfetto SQL(stdlib)做校验。
快速上手:3 步抓取与查看 Binder Trace
以下按 Android 12 及以上设备写命令,以 Android 17 为当前参照。配置文件路径和拉取 Trace 的方法见第 2 篇。
抓取 Trace:
1
2
3
4
5
6
7
8
9
10
11# Android 12+:推送文本配置
adb push binder_config.pbtx /data/misc/perfetto-configs/binder_config.pbtx
# 开始抓取
adb shell perfetto --txt -c /data/misc/perfetto-configs/binder_config.pbtx \
-o /data/misc/perfetto-traces/trace.pftrace
# ... 操作手机复现卡顿 ...
# 取出文件
adb pull /data/misc/perfetto-traces/trace.pftrace .打开 Trace:访问 ui.perfetto.dev,拖入 trace 文件。
添加关键视图:
- 左侧点击 Tracks → Add new track
- 搜索 “Binder”,添加 Android Binder / Transactions 和 Android Binder / Oneway Calls
- 搜索 “Lock”,添加 Thread / Lock contention(如果有数据)
其他 Binder 分析工具
除了 Perfetto,还可以用两个工具辅助定位:am trace-ipc(系统自带)和 binder-trace(开源,能力更强但门槛更高)。
am trace-ipc:Java 层 Binder 调用追踪
am trace-ipc 用于追踪 Java 层 Binder 调用堆栈。系统会在目标进程开启 Binder stack tracking(BinderProxy.transact() 路径),在停止时导出文本统计。优点是零配置、无需 root。
基本用法很简单,就是”开始 → 操作 → 停止导出”三步:
1 | # 1. 开始追踪(系统会记录符合条件进程的 Binder 调用,通常以可调试进程为主) |
导出结果是纯文本,示例如下:
1 | Traces for process: com.example.app |
它会按进程分组,列出调用堆栈和次数(Count),适合快速回答“调了哪些服务、调了多少次”。
与 Perfetto 配合使用:Perfetto 看时间线与线程关系,trace-ipc 补“具体是哪个 Java 调用点发起调用”。
适用场景:怀疑卡顿/ANR 与频繁 IPC 有关,或需要定位具体 Java 发起点。
binder-trace:实时 Binder 消息解析
binder-trace 可以实时拦截并解析 Binder 消息,常被称为“Binder 的 Wireshark”,能看到接口、方法及部分参数。
它基于 Frida 动态注入,通常需要 root(或模拟器)和 frida-server,本地需 Python 3.9+。示例:
1 | # 追踪指定应用的 Binder 通信(-d 指定设备,-n 指定进程名,-a 指定 Android 版本) |
它支持按接口/方法/事务类型过滤,适合安全研究和逆向分析这类“看消息内容”的场景。日常性能排查通常仍以 Perfetto + am trace-ipc 为主。
Binder 分析工作流
拿到 Trace 后,不要直接在大海捞针。推荐按照“找目标 → 看耗时 → 查线程 → 找锁”的顺序进行。
步骤一:定位事务耗时
先在问题窗口里找目标事务。已知发起进程时,从 Transactions 轨道筛选 Client;已知接口时,搜索 IActivityManager、attachApplication 或完整 slice 名。排查 UI 卡顿时,也可以从主线程一段长 Sleeping 开始,但 Sleeping 只说明线程在等待,必须有 Binder slice、flow 或调用栈重合,才能把这段等待归到 Binder。
选中一个 Transaction Slice 后,右侧的 Details 面板会显示这次事务的详细信息(Client/Server 线程、时间戳、耗时等)。不同版本的 Perfetto UI 字段名可能略有差异,但你可以用 Perfetto SQL 的 android_binder_txns 来统一理解几个关键耗时:
client_dur:客户端端到端耗时(同步调用时基本等同于“我在等这次 Binder 返回”的时间)server_dur:服务端从开始处理到(同步时)发出 reply 的 wall clock 时长dispatch_dur = server_ts - client_ts:从客户端发起到服务端开始处理的延迟(可能包含排队和调度等待)
下面这段 SQL 可以直接在 Perfetto UI 的 SQL 页面运行(用于快速找出最慢的同步事务,并拆出派发延迟与服务端耗时):
1 | INCLUDE PERFETTO MODULE android.binder; |

client_dur 很长、server_dur 很短时,先核对 dispatch_dur 是否占了大头,再检查服务端线程池与调度。server_dur 本身很长时,跳到服务端 Binder 线程,分开看 Running、锁等待、下游 Binder 和 I/O;不能只凭总耗时给服务端代码定责。
步骤二:评估线程池与 Oneway 队列
如果步骤一的分析发现耗时主要不在服务端处理,而是在”排队”上,那就需要进一步检查 Binder 线程池的状态了。在深入分析之前,先回答一个经常被问到的问题:**”每个进程大概会有多少个 Binder 线程?system_server 的 Binder 线程池规模大致是什么量级?什么情况下会’耗尽’?”**
system_server 的 Binder 线程池规模
在上游 AOSP 的 Binder 实现中,线程池的设计思路是:按需增长、可配置、没有单一固定数字。
- 线程池是按需增长的:服务端 Binder 线程按需创建,达到配置上限后不再增加;已创建的线程通常持续到进程结束,不会随负载下降而自动缩回。上限由内核中的
max_threads字段和用户态ProcessState#setThreadPoolMaxThreadCount()等配置共同决定。 - 典型上限取决于进程角色:普通应用进程的 Binder 工作线程上限通常在 15 左右(libbinder 默认值);但
system_server会在启动时显式把上限调高,AOSP 当前代码中设置为 31。因此,system_server并不等同于“默认十几条线程”。
某些厂商 ROM 或定制内核会根据自身负载模型,把上限调大或调小(例如调到几十条线程),因此你在不同设备上通过ps -T system_server、top -H或 Perfetto 数Binder:线程时,看到的具体数字可能会有差异。 - 以实际观测为准,而不是死记一个数字:在 Perfetto 里,更推荐的做法是直接展开某个进程,看有多少个
Binder:xxx_y线程轨道,以及它们在抓 Trace 期间的活跃程度,以此来评估线程池的“规模”和“繁忙度”。
Binder 线程数、缓冲区与“Binder 耗尽”
在性能分析中,大家提到“Binder 个数”时,往往会混在一起谈三类不同的资源限制:
Binder 线程池耗尽要同时看活跃线程数、每条线程是否正在处理事务,以及新事务的派发延迟。线程处于 S 不一定忙,也可能是空闲 Binder 线程在等新工作。Client 长时间 Sleeping、dispatch_dur 变大,只能说明服务端开始处理前有等待;还要排除调度延迟、事务串行化等原因,才能归因到线程池上限。
Binder 事务缓冲区耗尽涉及每个进程共享的有限缓冲区;Android 文档给出的当前大小为 1 MB,正在传输的事务共同占用。单次传输大 Bitmap、长字符串或数组,以及并发事务尚未释放,都可能让请求或回复失败。遇到 TransactionTooLargeException 时,先缩小请求和返回的 Parcel;大块数据可通过 SharedMemory、文件或 ParcelFileDescriptor 传递。
Binder 引用表 / 对象数量方面,Binder 驱动会为每个进程维护引用表和节点对象,这些也有上限,但在大多数实际场景中,很少首先撞到这里。常见风险是长时间持有大量 Binder 引用却不释放,更多体现为内存/稳定性问题,而不是 UI 卡顿。
在 Perfetto 里分析时,可以带着一个判断框架:
“现在的慢,是因为线程池被打满,还是事务过大/缓冲区被用光?”
前者主要看 **Binder 线程数与它们的 thread_state**,以及事务的 dispatch_dur(server_ts - client_ts,可近似理解为派发/排队延迟);后者则关注 单次事务的大小、并发事务数量和是否伴随 TransactionTooLargeException / binder_transaction_alloc_buf 相关日志。
现在回到我们的分析场景:
Binder 线程池的繁忙程度直接决定了服务的并发处理能力。对于同步事务来说,如果服务端 Binder 线程长期处于 Running 或 Uninterruptible Sleep (D) 状态,新的请求就会在内核里排队,客户端线程会长时间阻塞在 ioctl(BINDER_WRITE_READ) / epoll_wait,主线程在 thread_state 上通常表现为长段 S(Sleeping)。
在 Perfetto 中诊断线程池问题,优先看两个信号:Binder 线程是否长期满载,以及事务的 **dispatch_dur 是否显著大于 server_dur**(判读方式与步骤一一致)。
关于 Oneway 调用在 Perfetto 中的识别:同步调用(Two-way)和异步调用(Oneway)在 Perfetto 中的表现有明显区别,学会区分它们对分析很有帮助。同步调用时,客户端会阻塞等待(thread_state 显示 S),Perfetto 通常会画出 transaction → reply 的 Flow;而 Oneway 调用客户端发完就返回、几乎无阻塞,Flow 只有单向的 transaction,没有 reply 回来。另外,Oneway 调用的 Slice 名称后面可能会带 [oneway] 标记;在 SQL 里也可以通过 android_binder_txns.is_sync = 0 来过滤 Oneway。
在分析 Oneway 相关问题时,重点关注两件事:一是服务端的队列深度(如果同一 IBinder 对象上的 Oneway 请求堆积,后续请求的实际执行时机会被不断延后);二是是否存在批量发送的模式(短时间内大量 Oneway 调用会形成”尖峰”,在 Perfetto 中表现为服务端 Binder 线程上密集排列的短 Slice)。

值得一提的是,SystemServer 的 Binder 线程要处理来自各个 App 的请求,也可能在处理请求时向其他进程发起嵌套 Binder 调用;AMS 与 WMS 同在 system_server,两者之间的普通方法调用不是跨进程 Binder。如果某个”行为不端”的 App 在短时间内疯狂发送 Oneway 请求,可能会把某个系统服务的 Oneway 队列塞满,进而影响到其他 App 的异步回调时延,造成全局性的卡顿感。
步骤三:排查锁竞争
服务端 Binder 线程在事务期间长时间处于 S 或 D,先查它在等什么。锁、Binder 嵌套调用和内核等待都可能造成这类状态;D 是不可中断睡眠,不等同于「Disk Sleep」。
Java 锁(Monitor Contention) 需要 ART 的 contention 事件来确认。blocked_function 出现 futex_wait 只说明线程走了 futex 等待,Native 锁等路径也可能如此。若采集了 dalvik 类别,可在 Lock contention 轨道或 android_monitor_contention 表里核对等待者、持有者、方法和持续时间。Perfetto 官方示例给出了相同的查询入口。

Native 锁(Mutex / RwLock) 的情况相对少见一些,但在某些场景下也会遇到。表现形式类似:线程状态为 D 或 S,但调用栈里出现的是 __mutex_lock、pthread_mutex_lock、rwsem 等 Native 层的符号,而不是 Java 的 futex_wait。分析这类问题通常需要结合 sched_blocked_reason 事件来看线程具体在等什么,属于比较进阶的内容,这里就不展开了。
使用 SQL 统计 system_server 中的 Java Monitor Contention(可选)
PerfettoSQL 标准库已经提供了解析后的 android_monitor_contention 表(由 ART 的 monitor contention 相关 Slice 解析而来),建议优先使用它来做统计,而不是手工解析 slice 名称字符串:
1 | INCLUDE PERFETTO MODULE android.monitor_contention; |
提示:如果查不到数据,请确认抓取时
atrace_categories包含dalvik,并且问题场景中确实发生了 monitor contention。

最新平台特性与优化建议
随着 Android 版本演进,Binder 在性能与稳定性上也持续增强。理解这些机制有助于解释 Perfetto 现象并指导优化。
Binder Freeze(Android 12+):Cached 进程被冻结后几乎不获得 CPU。对其发起同步 Binder 调用会被拒绝,并可能触发目标进程终止;异步(oneway)事务通常先缓冲,待解冻后处理。
Frozen-callee 回调策略(Android 16+):Android 16(API 36)为 RemoteCallbackList 新增了 Builder 和 frozen callee policy(FROZEN_CALLEE_POLICY_DROP、FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT、FROZEN_CALLEE_POLICY_ENQUEUE_ALL),用于控制目标进程被冻结期间回调的堆积方式,降低解冻后的抖动与压力。
Binder Heavy Hitter Watcher:用于识别短时间内占比异常高的 Binder 调用热点。启用方式、阈值和输出渠道依版本与设备配置而定。
给开发者的一些建议:
关于 Oneway:只在确实不需要返回值和完成时机时使用(如日志、状态通知)。把同步调用硬改成 Oneway 往往只会把等待转移到服务端队列,并引入时序问题。
关于 大数据传输:避免直接走 Binder(尤其是 Bitmap)。单进程 Binder 缓冲区约 1MB,容易触发 TransactionTooLargeException;应改用 SharedMemory、文件或 ParcelFileDescriptor。
关于 主线程调用:不要在 UI 线程调用耗时不可控的 Binder 服务;若必须调用,请放到后台线程,完成后再回主线程更新 UI。
总结
从主线程上的一次 Binder 等待出发,先核对事务是否与该等待重合,再比较 dispatch_dur 与 server_dur。派发前慢就查线程池和调度;服务端执行慢就看 Running、Sleep 与锁事件。缺少 Binder tracepoints 或 ART contention 事件时,报告里把原因保留为待查,不用一条线程状态代替完整归因。
参考
- 理解Android Binder机制1/3:驱动篇
- PerfettoSQL stdlib - android.binder
- Perfetto Documentation - Ftrace
- Android Source - Binder
- Android Developers - Parcel and Bundle
- binder-trace - Wireshark for Binder
- am trace-ipc 源码分析
关于我 && 博客
一个人可以走的更快 , 一群人可以走的更远
