本文是 Systrace 系列文章的第十篇,主要是对 Systrace 中的 Binder 和锁信息进行简单介绍,简单介绍了 Binder 的情况,介绍了 Systrace 中 Binder 通信的表现形式,以及 Binder 信息查看,SystemServer 锁竞争分析等
本系列的目的是通过 Systrace 这个工具,从另外一个角度来看待 Android 系统整体的运行,同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章,但是总是记不住代码,或者不清楚其运行的流程,也许从 Systrace 这个图形化的角度,你可以理解的更深入一些。
版本说明(2026-10-13 核查):本文保留 2019 年 Systrace 案例。Java 锁和 Binder 的分析方法仍有参考价值;旧查看器界面、Framework 行号及设备时序不适用于所有版本。代码以 AOSP Android 10
android-10.0.0_r1对照,ART 计数另核对 Android 9 和 Android 16;截图来自厂商系统,不能据行号认定它就是上述 AOSP 构建。新 Trace 可结合文中 Perfetto 入口分析。
本文目录
系列文章目录
- Systrace 简介
- Systrace 基础知识 - Systrace 预备知识
- Systrace 基础知识 - Why 60 fps ?
- Systrace 基础知识 - SystemServer 解读
- Systrace 基础知识 - SurfaceFlinger 解读
- Systrace 基础知识 - Input 解读
- Systrace 基础知识 - Vsync 解读
- Systrace 基础知识 - Vsync-App :基于 Choreographer 的渲染机制详解
- Systrace 基础知识 - MainThread 和 RenderThread 解读
- Systrace 基础知识 - Binder 和锁竞争解读
- Systrace 基础知识 - Triple Buffer 解读
- Systrace 基础知识 - CPU Info 解读
- Systrace 流畅性实战 1 :了解卡顿原理
- Systrace 流畅性实战 2 :案例分析: MIUI 桌面滑动卡顿分析
- Systrace 流畅性实战 3 :卡顿分析过程中的一些疑问
- Systrace 响应速度实战 1 :了解响应速度原理
- Systrace 响应速度实战 2 :响应速度实战分析-以启动速度为例
- Systrace 响应速度实战 3 :响应速度延伸知识
- Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇
- Systrace 线程 CPU 运行状态分析技巧 - Running 篇
- Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇
Binder 概述
本文对应新的 Perfetto 10:Binder 调度与锁竞争。旧 Systrace 里主要通过 Flow Events、Binder Slice、锁竞争文本来定位调用方和被调用方;Perfetto 里仍然要看这些关系,但入口会分散到 Binder 相关 Slice、线程状态、CPU 调度、Flow/关联关系和 SQL 查询里。
现在分析 Binder 卡顿时,推荐先按旧文章的方法建立调用关系:谁调用谁、调用持续多久、调用端在等什么。然后结合 Perfetto 继续检查:调用端是否 Sleep 等 Binder 返回,被调用端是否 Running,是否在 SystemServer 的锁上排队,Binder 线程池是否繁忙,同一时间 CPU 是否被别的任务占满。这样可以把“Binder 慢”拆成远端执行慢、锁竞争、线程池排队、CPU 调度慢这几类不同原因。
Android 的大部分进程间通信都使用 Binder,这里对 Binder 不做过多的解释,想对 Binder 的实现有一个比较深入的了解的话,推荐你阅读下面三篇文章
我把 Binder 和锁单独拿出来讲,是因为分析卡顿和响应慢时,经常需要顺着这条线往下查。跨进程同步调用会把远端等待传递给调用方。例如 SystemServer 的某个服务正在等待 Java 锁,应用主线程发出的同步调用就可能跟着变慢。本文的 monitor contention 是 ART 记录的 Java 对象监视器竞争,不是 Binder 驱动内部锁;android.display 等非 Binder 线程也会参与同一把锁的竞争。
dequeueBuffer 耗时还可能来自可用 Buffer 不足、GPU/fence 或消费端处理延迟。其是否经过远端 Binder、消费端在哪个进程,要按系统版本和 BufferQueue 拓扑核实;不能把长 dequeueBuffer 一律归因于 SurfaceFlinger 主线程阻塞。
读图前先区分三种调用:远端同步调用要等服务端处理并回复,返回类型为 void 也一样;远端 oneway 不等服务端执行完成,但仍有提交开销和服务端排队;同进程 AIDL 调用可能直接在调用线程执行,oneway 不会把本地调用自动搬到线程池。嵌套同步 Binder 还可能重入调用链上的原线程。AIDL 调用规则、Binder 线程模型说明了这些区别。
这里放一张文章里面的 Binder 架构图 , 本文主要是以 Systrace 为主,所以会讲 Systrace 中的 Binder 表现,不涉及 Binder 的实现

Binder 调用图例
Binder 主要用于跨进程通信。下面是 SystemServer 与高通 vendor.qti.hardware.perf@2.0-service 的一次旧设备通信示例:

图中的客户端、服务端区间都是时间线上的耗时,不能直接当成 CPU 执行时间。服务端尚未接收事务、接收后等待调度、等锁、执行代码,要结合 Flow 和线程状态分别看。架构图里的 Java/JNI 路径也不覆盖所有 native/HIDL 调用。
旧 Systrace 查看器右上角 ViewOptions → Flow Events 可以显示已采集到的关联箭头;打开这个选项不会补出采集时缺失的 Binder 事件。

点击 Binder Slice 查看详细信息:

这张图里,Transaction Id=334303 用来关联发送与接收事件;Calling PID=10661 对应发起线程 TID,Calling tgid=1605 才是进程 ID。附件的原始事件也记录了这两个值。Dest Process=867 是目标进程,发送时 Dest Thread=0 不能解释为由 Linux 线程 0 执行,需沿接收事件找实际线程。
Flags=0x10 是允许回复携带文件描述符的 TF_ACCEPT_FDS,没有 TF_ONE_WAY=0x01 位;这次是同步事务。Code=0x6 需按对应接口及版本解码,查看器的 “Java Layer Dependent” 提示不能单独证明 Java 方法名。Wall Duration=11.739 ms 包含等待,Self Time=11.363 ms 是扣除子 Slice 后的区间时间,也不等于 CPU 时间。Binder UAPI 标志定义可用于核对这些标志位。
下面重点看 Java 锁信息和锁等待。找到关联线程后,还需要返回对应版本的源码确认它们争用的是同一个对象。
Systrace 显示的锁的信息

monitor contention with owner Binder:1605_B (4667) at void com.android.server.wm.ActivityTaskManagerService.activityPaused(android.os.IBinder)(ActivityTaskManagerService.java:1733) waiters=2 blocking from android.app.ActivityManager$StackInfo com.android.server.wm.ActivityTaskManagerService.getFocusedStackInfo()(ActivityTaskManagerService.java:2064)
上面的话分两段来看,以 blocking 为分界线
第一段信息解读
monitor contention with owner Binder:1605_B (4667) at void com.android.server.wm.ActivityTaskManagerService.activityPaused(android.os.IBinder)(ActivityTaskManagerService.java:1733) waiters=2
Monitor 是与 Java 对象关联的同步机制。同一时刻只能由一个线程持有,同一线程可以重入。实例 synchronized 方法锁住 this,静态 synchronized 方法锁住对应的 Class 对象;同步代码块则锁住表达式指向的对象。它不是字面意义上的“两个池”。Object.wait() 的 wait set 与进入 synchronized 时的竞争要分开:前者会释放该对象的监视器,恢复前还要重新获取。见 JLS 17.1–17.2。
Owner 是持锁线程,这里是 Binder:1605_B,Linux TID 为 4667,不是持锁的“对象”或“方法”。
at 是 ART 记录的持锁位置。Android 10 中,该位置在获取或膨胀监视器等路径记录,不是持续更新的线程调用栈。持锁线程此刻也可能正等待另一笔 Binder 调用,不能只看 at 就断定它正在 CPU 上执行该行。
图里位置是 activityPaused 的 ActivityTaskManagerService.java:1733。下面是 AOSP Android 10 对照方法;该标签的同步块在第 1694 行,与厂商截图的行号不同:
1 |
|
这里锁的是 mGlobalLock。其他线程访问同一个对象的同步块时,需要等它释放并成功竞争到锁;持锁线程自己重入不在此列。退出同步块就执行解锁,不必等整个方法返回;因异常退出同步块也会解锁。
waiters 是本次等待注册前读取的监视器等待者计数,不包含当前这一位。Android 9/10 先保存 num_waiters_ 再递增,Android 16 用返回旧值的 fetch_add。本例 waiters=2 对应此前两位加上当前 android.bg,共三位参与等待。这个字段不是 FIFO 序号,也不会随整个 Slice 实时更新;ART 的 Object.wait() 路径也使用这个计数,不能把任意 Trace 的该值都等同于纯粹的入口等待队列长度。
第二段信息解读
blocking from android.app.ActivityManager$StackInfo com.android.server.wm.ActivityTaskManagerService.getFocusedStackInfo()(ActivityTaskManagerService.java:2064)
第二段标识当前被阻塞线程所在的方法:这里是 android.bg 线程中的 ActivityTaskManagerService.getFocusedStackInfo。AOSP Android 10 对照代码如下,其同步块在第 2027 行;截图中的第 2064 行属于原设备源码:
1 |
|
这里同样是 synchronized (mGlobalLock)。两个方法竞争的是同一锁对象,不能把它替换成 ActivityManagerService.this 来解释。
总结
这条记录表示:android.bg 在 getFocusedStackInfo 中获取监视器受阻,记录时的持锁线程是 Binder:1605_B,持锁位置指向 activityPaused。当前线程要等监视器可用并竞争成功后才能继续。标题中的 owner 是记录时的快照,不能保证整个等待区间都由它持锁。
原始信息中的 owner、持锁位置、等待计数和阻塞位置分别是:
1 | monitor contention with owner Binder:1605_B (4667) |
等锁分析
还是这份 Trace,我们一步步把顺序理清:先看 Binder:1605_B 自己的等待,再看它成为持锁线程后的影响:

这张图选中的 monitor contention 实际表示 Binder:1605_B 也曾等待 android.anim 持有的锁,位置是 WindowAnimator.animate,持续约 0.050 ms。之后它获得锁,其他线程才开始等待它。它内部还有 Binder 调用,整段持锁时间也不能等同于持续 Running。
锁等待

下面三条记录分别来自不同线程,原图中的线程名复制错误在这里更正。附件原始 ATrace 的 Begin/End 区间为:
| 等待线程 | 阻塞方法 | 标题中的 waiters | 该等待 Slice 的 Wall Duration |
|---|---|---|---|
| android.display(1683) | ActivityMetricsLogger.checkVisibility | 0 | 3.647 ms |
| android.anim(1684) | WindowManagerService.relayoutWindow | 1 | 2.559 ms |
| android.bg(1692) | ActivityTaskManagerService.getFocusedStackInfo | 2 | 3.835 ms |
三条记录开始时都指向 Binder:1605_B (4667) 持有的 mGlobalLock。它们部分重叠,不能把三项相加当成一次调用耗时。线程在等待期间可能睡眠、被唤醒后处于 Runnable、重新运行再尝试获取锁;Java 的阻塞状态与内核调度状态不能直接画等号。
锁释放

原图标注的唤醒链可以在附件中找到:Binder:1605_B → android.display → android.anim → android.bg。但“被唤醒”只说明线程有机会继续运行,不能单凭这条链证明它立刻获得锁,或者证明 Java 监视器按 FIFO 分配。
附件还保留了图中容易漏掉的后续竞争:android.display 在 checkVisibility 那段等待结束后,又在 WindowProcessController.hasActivitiesOrRecentTasks 中等待 android.anim,持续 3.808 ms;随后 android.anim 在 Session.getInTouchMode 中等待 android.bg,持续 3.148 ms。因此,这不是三个线程各等待一次、依次完成的六步流程。android.bg 的原始等待标题仍写着 Binder owner,也说明不能把标题快照当成整个区间的持锁历史。
分析时同时看等待 Slice 的起止、调度状态、唤醒者、后续 owner 和对应源码。下面是另一段较长的 SystemServer 锁竞争示例,进程和 owner 已经不同,不能接到上面的时序中:

这么多线程一起等锁,看着确实有点吓人。多线程同时等锁会放大启动或交互延迟,但这张图不能证明“应用装得多、手机用得久就会因此变卡”,也不能推出所有系统的等待者经常达到 7–10 个。需要继续查持锁线程在做什么:执行耗时工作、等远端 Binder、等 I/O,还是等待 CPU 调度。
优化应先减少可避免的临界区工作,并在保证共享状态一致性的前提下调整锁内跨进程调用。简单增加 Binder 线程数不会消除同一把锁的串行瓶颈;把接口改成 oneway 则会改变完成时机和错误反馈,不能作为通用修复。
查看唤醒信息的方法见:Systrace 中查看线程唤醒。
相关代码
下面两段来自 AOSP android-10.0.0_r1 的 art/runtime/monitor.cc,用于解释旧图格式,不是可单独编译的示例。
Monitor 信息
PrettyContentionInfo 负责拼接 owner、记录的持锁位置及等待计数:
1 | std::string Monitor::PrettyContentionInfo(const std::string& owner_name, |
Block 信息
以下是 Monitor::Lock 的连续节选。注意先保存计数再递增,以及只有 ATrace 启用且能取得 owner 时才发出这段事件:
1 | ArtMethod* owners_method = locking_method_; |
Android 16 android-16.0.0_r1 仍有 monitor contention 埋点,但计数已改为原子 fetch_add,owner 信息获取也有变化;不要把这段 Android 10 实现直接当成当前实现。Trace 里没看到这类事件,不足以证明没有锁竞争,还要核实采集类别、埋点条件和数据完整性。
用 Perfetto 分析新 Trace 时,先关联 Binder 两端,再对齐同一时段的线程状态和 monitor contention。当前标准库提供 android.binder 与 android.monitor_contention;表和字段以所用 Trace Processor 版本为准,前提是相关事件已采集。缺少关联或没有解析出记录,要保留为未知,不能填成零耗时或零竞争。
参考
附件
附件为 App_lunch.html.zip,包含旧 Systrace HTML。本次按文本核对了其中的 Binder、monitor contention 和调度事件,未执行 HTML 内脚本。旧 HTML 查看器能否在当前 Chrome 正常交互需另行确认;分析时使用兼容其格式的查看器或导入方式。
点此链接下载文章所涉及到的 Systrace 附件
关于我 && 博客
下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师!
- 博主个人介绍 :里面有个人的微信和微信群链接。
- 本博客内容导航 :个人博客内容的一个导航。
- 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可)
- Android性能优化知识星球:付费社群已于 2026 年停止运营,不再招收新成员;此处保留历史介绍。
一个人可以走的更快 , 一群人可以走的更远
