做性能分析这行手里没一两件趁手的性能分析工具真就寸步难行。这两年我用过不少工具最后长期留着的主力反而是这个谷歌开源、看起来有点“硬核”的 Perfetto。它既能抓 App 的启动耗时、掉帧卡顿也能看 CPU 调度、内存分配、功耗变化甚至能对着时间线把“哪个线程在哪个时刻干了什么”翻个底朝天。如果你搞 Android 开发、系统优化、大型软件性能排查或者只是好奇手机里每个进程到底在跑什么这篇就是给你准备的。我会从安装命令开始把抓取、导入、读图、SQL 分析这条完整链路讲透包括我踩过的坑。1. 内容整体设计与思路拆解1.1 Perfetto 到底是什么不只是 systrace 的替代品很多人第一次听说 Perfetto是在寻找 systrace 替代方案的时候。旧 Android 系统上的 systrace 已经停更而 Android 10 开始系统自带的性能追踪底座就已经换成了 Perfetto。它是一套基于 Linux 内核 ftrace 的开源追踪平台同时把用户态的 atrace、内存分析用的 heapprofd、电量统计对应的 power 数据源全部收编进一条统一的 trace 文件里。用生活里的话说systrace 是一台老式行车记录仪能录下画面但画质一般、存储格式老旧Perfetto 则更像是“行车记录仪 黑匣子 远程监控”的组合体。它记录的不只是图形渲染那一层而是从内核调度器到 App 代码里埋点切片的完整数据流。你最终拿到的一个 .perfetto-trace 文件里面既有线程在 CPU 上的运行状态也有 CPU 频率变化、进程内存占用、Binder 调用、Choreographer 帧渲染标记甚至你自己手动埋的 Trace 段。所以它真正能解决的是传统“看 log 猜原因”没法搞定的问题卡顿到底发生在哪个线程为什么 UI 线程被阻塞了 200ms是 GC 导致还是 Binder 等待这些问题在 Perfetto 的 UI 里都能从时间线上直接看出来。新手拿它做启动耗时分析老手拿它做系统级调度诊断覆盖面极广。1.2 为什么越来越多团队开始换用 Perfetto我用 Perfetto 之前也纠结过systrace 用得好好的真有必要学新工具吗但对比一圈后答案很清楚。首先Perfetto 的数据完整性远高于 systrace。systrace 能看到的调度、渲染标签Perfetto 基本都有但反过来 Perfetto 还能看更内部的信息比如 CPU 空闲状态idle、频率调节freq、内存页错误、堆内存分配这些是 systrace 完全没有的。做性能优化很多时候查到最后都是内核或者系统资源的问题没有这些低层数据就只能停留在“猜测”阶段。其次它的数据处理能力非常强。Perfetto 自带 Trace Processor 引擎支持用 SQL 查询 trace 里任何事件。你可以写一句“SELECT * FROM slice WHERE name LIKE %bindView%”就把所有 bindView 切片捞出来统计耗时分布。systrace 的文本解析还得靠脚本一轮轮洗Perfetto 直接把“算数”这个能力开放给了用户。第三它跨平台、跨场景。Android 上能用Chrome 浏览器性能分析也能用Linux 系统自带 perfetto 命令也能抓。团队里有人用 Windows、有人用 macOS只要打开浏览器访问 ui.perfetto.dev拖入 trace 文件就能看不需要装一堆 GUI 工具。对我来说这才是日常效率提升最大的点。1.3 事件从手机到时间线的完整流动路径理解 Perfetto 的工作流是后续排查问题的基础。它整体分三块数据生产者、后台守护进程、数据消费者。抓取时手机端的 perfetto 服务根据你配置的数据源比如 sched、freq、gfx、view从内核 ftrace 和用户态的不同模块收集事件再打包写入 trace 文件。电脑端的 ui.perfetto.dev 或者本地 trace_processor 负责解析这份文件生成可视化的时间线。这个过程很像拍电影内核和 App 的各个模块是演员perfetto 服务是摄影师它只管忠实地记录每一次调度、每一个 slice 的起止时间而 UI 就是剪辑台你可以在上面任意缩放、搜索、标注最后用 SQL 把“演出时长”“出镜次数”统计成表格。我之所以先讲这条链路是因为很多朋友抓完数据后一脸懵不知道哪些数据从哪来、该怎么筛。搞清了源头后面读时间线时的很多疑问会迎刃而解。下面开始真正动手从最简单的命令抓取到 UI 读图再到 SQL 分析我把每个步骤都展开讲。2. 核心细节解析与实操要点2.1 先跑通第一条命令命令行抓取拿到任何新工具我的习惯是先跑通一个最小用例。Perfetto 的最小用例用 adb 就能完成不需要 root甚至不需要电脑端安装任何软件前提是手机系统自带 perfetto 可执行文件Android 10 以上基本都有。最简单的一条抓取命令长这样adb shell perfetto --text -o /data/misc/perfetto-traces/start_trace.txt -t 5s sched freq idle拆开看几个关键部分--text输出文本格式的 trace方便快速确认数据有没有抓到。实际分析时建议去掉这个参数输出二进制 .perfetto-trace 文件体积小、信息全UI 解析也更稳定。-o输出路径。注意一定要写到/data/misc/perfetto-traces/目录这个目录是 perfetto 服务默认有权限写的路径直接写/sdcard/在某些系统上反而会因为 SELinux 策略失败。-t 5s抓取时长。到时间后命令会自动停止并落盘不用手动打断。如果不加这个参数需要按 CtrlC 停止。后面的sched freq idle是数据源关键字。sched记录线程调度切换freq记录 CPU 频率变化idle记录 CPU 空闲状态。这三个是系统级分析的标配组合已经能满足大部分 CPU 调度类问题。抓完之后用 adb pull 拉到电脑上adb pull /data/misc/perfetto-traces/start_trace.txt .然后打开 ui.perfetto.dev把文件拖进去。如果一切正常你能看到一条条彩色的时间线这就是第一个有效 trace。我建议第一次抓的时候顺手打开一个 App 滑一滑、点一点让 trace 里有实际业务事件后面练习读时间线时才有对象。2.2 图形化录制与代码埋点两条日常最常用通道命令行抓取适合脚本化和远程操作但日常调试时我更常用图形化录制手机上打开“开发者选项 - 系统跟踪”会进入一个录制配置界面。你可以勾选要记录的类别比如 Graphics、View System、Power、Audio点击录制后正常操作 App再下拉通知栏停止系统会把 trace 文件保存到/data/local/tmp/或者弹出分享窗口。这个方式的优点是门槛极低测试同学也能用不需要理解任何命令。但图形化录制的问题是没办法精确控制抓取起点。想从 App 点开图标那一刻开始抓还是得配合命令行或者自动化脚本。这时候就需要另一种通用手段代码埋点。Perfetto 支持在应用里手动埋 Trace 段逻辑上很像给代码里插桩打标记import android.os.Trace; Trace.beginSection(bindView); try { // 实际业务逻辑 Thread.sleep(100); } finally { Trace.endSection(); }只要 App 在 debug 版本下开启了android:debuggabletrue这段逻辑执行时产生的“bindView”切片就会出现在 trace 里。x 运行到慢代码附近就能精确看见这段时间里发生了什么。高级一点可以用Trace.beginAsyncSection做跨线程异步追踪但日常优化用同步版本足够。埋点有两个小细节容易踩坑第一生产环境建议把Trace类调用包在自定义开关后面避免包体或性能损耗第二部分系统版本对Trace支持有差异最低建议 Android 4.3 及以上实际项目中 8.0 以上埋点才比较省心。2.3 配置文件决定数据深度与体积的“配方”很多人第一次抓 Trace默认命令抓完发现文件巨大一个 10 秒的 trace 能到几百 MB。这其实不是工具问题而是你没控制好“配方”。Perfetto 支持通过-c参数指定一个配置文件在里面严格定义 buffer 大小、抓取时长、数据源开关。比如下面这个精简配置duration_ms: 10000 buffers { size_kb: 65536 } data_sources { config { name: linux.ftrace ftrace_config { ftrace_events: sched/sched_switch ftrace_events: power/cpu_frequency ftrace_events: sched/sched_wakeup } } }把它保存成config.pbtxt然后执行adb shell perfetto -c config.pbtxt -o /data/misc/perfetto-traces/custom_trace.perfetto-trace配置里的buffers.size_kb决定缓冲区总量。理论上设置的 buffer 越大越不容易在抓取过程中丢数据但文件也会膨胀。我的经验值是单纯看 CPU 调度32MB 足够要连应用内存、帧渲染一起抓建议 64-128MB否则抓 10 秒都可能因为缓冲满了丢掉后半段。duration_ms控制在 10000-30000 比较好太短看不出规律太长文件处理也慢。数据源的选择更讲究。sched_switch是必须的它记录每次线程切换cpu_frequency对照 CPU 频率sched_wakeup能看出一个线程是被谁唤醒的排查“线程等待太久”时很有用。如果只想看图形渲染就只保留gfx、view和sched_switch别一股脑全开不然几秒钟就能抓出 1GB 数据读图时根本无从下手。3. 实操过程与核心环节实现3.1 完整案例抓一次 App 冷启动 trace纸上谈兵没意思这里我带大家完整抓一次 App 冷启动 trace这也最贴近日常需求。目标是弄明白从用户点击 App 图标到首页首帧系统到底做了哪些事。首先准备环境# 确保手机已连接 adb adb devices # 把目标 App 强制杀掉模拟冷启动 adb shell am force-stop com.example.demo然后启动 perfetto 抓取时长定 10sadb shell perfetto -o /data/misc/perfetto-traces/cold_start.perfetto-trace -t 10s sched freq idle gfx view注意观察命令行的输出正常会出现 “start tracing” 提示。这个时候立即启动目标 App。比较稳的做法是借助 monkey 或者脚本自动触发adb shell monkey -p com.example.demo 1让 App 自己跑完首屏加载等 10 秒抓取结束再把文件拉回电脑adb pull /data/misc/perfetto-traces/cold_start.perfetto-trace .把这个文件拖进 ui.perfetto.dev 后你应该会看到类似这样的时间线结构最上层是 CPU 核心区域每核一条泳道展示线程在哪个核上运行中间是进程和线程区域你能看到com.example.demo进程下有很多线程包括主线程、RenderThread、Binder 线程等再往下是各种 ftrace 事件和计数组件。第一次打开可能会懵因为信息太多了。我的实操经验是先别管其他直接在搜索框里搜ActivityThread或者performLaunchActivity这个 slice 是 Activity 启动的核心再搜Choreographer#doFrame这个 slice 是每一帧开始的关键。你搜到后时间线上会高亮对应区间再从高亮点向两侧展开就能逐步拼凑出启动链路里哪段耗时最长、哪个线程在空转等待。3.2 UI 界面怎么读时间线、切片与计数器用惯视频剪辑软件的人看 Perfetto 的 UI 应该非常有亲切感。主区域就是一条彩色时间线从上到下按 CPU、进程、线程分轨。每个长条就是一个 slice相当于一段代码或一个事件的执行时间。不同颜色代表不同线程或不同事件类型。时间轴可以拖动、缩放蓝色区域是 buffer 使用量黄色块是 CPU 调度时间这种直观性是传统 log 无法比拟的。读图的顺序我建议固定一个套路先看整体概览把时间线缩到最短找最明显的“缺口”或“长条”。比如启动过程中如果主线程那个泳道出现一大段空白或者很长的 slice那基本就是卡顿点。然后逐步放大这段区域看关联线程在干什么是不是有 Binder 调用、锁等待、GC 等。再回到 CPU 频率轨道看这段时候 CPU 是不是降频了。细节上要注意 slice 的颜色深浅、边框类型代表不同状态。比如线程状态显示中绿色常表示 Running、蓝色表示 Runnable、橙色表示 Sleeping。这些状态颜色可以在 UI 的图例里查看刚开始记不全也没关系遇到不确定的 slice点击它右侧详情面板会列出准确的事件名、线程名、时间戳和历时。千万别忽视 UI 最下面的“SQL”面板入口以及“Search”按钮。搜索框支持模糊搜索 slice 名像我前面提到的doFrame、layout、inflate都是高频搜索词。合理使用搜索能把海量数据迅速聚焦到相关切片上效率比肉眼一行行扫高十倍。3.3 深入 SQL 分析把花哨界面变成能算数的数据表如果 Perfetto 只有可视化时间线那它顶多是个更漂亮的 systrace。它真正的护城河是可以对 trace 里的结构化数据跑 SQL 查询。点击 UI 下方的 “Query” 按钮会打开一个 SQL 输入框。此时底层数据已经被 Trace Processor 解析成一张张表其中最重要的表是slice、thread、process和counter。slice表里每一行就是一个切片包含ts开始时间戳、dur持续时间单位微秒、name、thread等信息。thread表保存线程名和 IDprocess表保存进程名和 PID。简单跑一条查询试试SELECT name, COUNT(*) AS cnt, AVG(dur) AS avg_dur FROM slice WHERE name LIKE %doFrame% GROUP BY name这条 SQL 能统计出所有doFrame切片的总数、平均耗时方便看首帧渲染是否拖沓。再复杂一点我想知道整个 trace 里耗时最长的 top 10 切片SELECT ts / 1000 AS ts_ms, name, dur / 1000 AS dur_ms, thread.name AS thread_name FROM slice INNER JOIN thread USING (utid) ORDER BY dur DESC LIMIT 10;这个查询对于快速定位长耗时任务特别有用。你会发现耗时大头往往不是 UI 线程的某个函数体而是它等待 Binder 返回、等待锁、或者被调度器延迟处理的时间。这类问题如果只用肉眼看时间线很难量化但在 SQL 里一行就能算清楚。备一个实用查询统计线程实际 Running 的时间与业务耗时之间的差值用来判断线程是否被频繁抢占。这里涉及sched视图Perfetto 文档里有专门说明但初期不用贪多学会用slice和thread表解决问题就已经超过一半人了。3.4 从一片混乱中锁定“罪魁祸首”一个掉帧排查示例看再多理论不如完整走一次排查。假设场景是App 里一个列表页面滑动时明显掉帧卡顿。用 Perfetto 该怎么定位抓取时别忘了带上gfx和view数据源因为掉帧问题通常和 Choreographer 渲染链路相关adb shell perfetto -o /data/misc/perfetto-traces/jank.perfetto-trace -t 15s sched freq idle gfx view边抓取边滑动列表结束后导入 UI。这一步我会直接搜索Choreographer#doFrame如果滑动过程有明显掉帧这个切片的名字后面往往会跟上jank等标记或者相邻两帧之间的时间间隔明显大于 16ms。定位到具体帧后把时间线放大到那一帧的范围看主线程在doFrame切片内部做了什么。常见嫌疑犯有三个一是layout或measure耗时太长说明 View 层级复杂二是Traversal延迟可能因为等待上一个 Binder 调用返回三是主线程排队在RenderThread前面GPU 处理不过来。在时间线上这些原因的表现各不相同如果是 Binder 等待你会看到主线程 slice 被拉得很长且等待超过几百毫秒如果是 GC则进程的其他线程或 GC 线程有明显活动。这时候再用 SQL 做个统计把这段滑动过程里所有大于 30ms 的 slice 全部捞出来SELECT name, COUNT(*) AS cnt, MAX(dur) AS max_dur FROM slice WHERE dur 30000 AND thread_name main GROUP BY name ORDER BY cnt DESC;跑出来的结果通常就是问题答案。我记得有一次排查这条 SQL 直接告诉我“GC 产生了上千次 30ms 以上的停顿”后续优化内存分配策略后卡顿立刻缓解。这就是 SQL 分析的意义不是猜而是让数据自己说话。4. 常见问题与排查技巧实录4.1 抓取失败、空文件、权限报错的快速解法命令行抓取最容易遇到的就是权限问题。Android 10 以上开发者选项里的“系统跟踪”功能打开后普通 App 和 adb 才能使用 perfetto如果设备没有解锁开发者模式或者 App 不是 debuggable命令行抓取就可能直接报错或者生成一个空文件。遇到这种情况先别慌按顺序排查确认手机已经开启“开发者选项 - 系统跟踪”或者直接adb shell settings put global settings_enable_monitor_phantom_procs 0这类操作看有没有报错。确认输出路径在/data/misc/perfetto-traces/内这个目录才是 perfetto 进程的合法写入区。确认抓取命令里没有同时开太多数据源导致 buffer 瞬间打满、文件还没来得及落盘就被杀掉。使用--text输出的同学注意文本 trace 解析速度慢如果是 10 秒以上的抓取建议直接用二进制输出减少文件体积和解析时间。另外抓取到一半命令没结束就拔线文件在手机上是残缺的导入 UI 会提示解析失败。这时候用adb shell perfetto --query看看系统有没有遗留的 trace 进程再重新抓一次即可别浪费时间修复坏文件。4.2 trace 文件巨大、版本不兼容、解析失败怎么办Perfetto trace 动辄几百 MB 甚至几个 G 是很正常的但真要拿这种东西去分析浏览器会卡到怀疑人生。我一开始就吃过这亏抓了 30 秒全数据源文件 1.2GB导入后滚动一步要等两秒。后来学乖了抓取前先想清楚要分析什么再精简数据源。排查 CPU 调度问题就别开 heapprofd只看启动流程只需sched加gfx。如果文件已经很大了也有补救办法。在 UI 左侧的 “Trace Processor” 面板中可以直接执行 “RUN METRIC” 或者手动写SELECT把关键数据导出成 CSV不用整条时间线拖来拖去。命令行的 trace_processor 也可以跑同样的 SQL适合批量处理多个文件生成对比报表。版本兼容问题也经常发生。老版本 Android 手机抓出的 trace在最新版 ui.perfetto.dev 上解析有时会提示版本过旧、部分工程化数据缺失。反过来新版抓出的 trace 放到旧版 UI 也可能打不开。我的建议是手机端 perfetto 版本尽量随系统更新电脑端直接用官方最新的 ui.perfetto.dev别自己本地部署一个很久不更新的旧版服务省得给自己挖坑。4.3 抓了几次数据时间线依然看不懂这是新手最常见的挫败点明明抓到了完整数据打开后却不知道从哪开始看。我的经验是别贪多嚼不烂先建立“标志性切片”清单。对每个项目提前想清楚这次要关注哪些关键节点。比如启动优化时我的清单就是Application#onCreate、ActivityThread#handleBindApplication、Choreographer#doFrame、ViewRootImpl#performTraversals。只要在 trace 里搜索这些名字找到对应的彩色块从这些块开始向外扩展你就能逐步理解系统流程而不是从几千条事件里大海捞针。另外建议一开始只分析单个进程。在 UI 的进程列表中右键点击目标进程选择“Pin”或“Only show this”把其他进程隐藏时间线简洁度会大幅提升。如果 App 是多进程架构再挨个进程查看主线程、渲染线程之间的消息传递。这个过程就像拆一个复杂的机械设备先把无关的齿轮挪开再盯着关键轴承转。4.4 几个我长期用下来的“真香”小技巧最后分享几个文档里不太写、但实战价值极高的小技巧。第一善用快捷键。在 ui.perfetto.dev 上按W放大、S缩小、A/D左右平移比鼠标拖动快得多。按G可以快速定位到当前搜索结果。这三个键用顺了读图效率直接翻倍。第二抓取时给 trace 加“锚点”。可以在操作 App 的同时用 adb 截个图记下准确时间或者在代码里埋一个标记切片这样回到时间线上能立刻对上“操作发生在哪个位置”。第三用 SQL 导出 CSV 写日报/周报。把多次抓取的启动耗时、掉帧次数统一导出成表格做对比分析时说服力极强。这比截图一张张发给老板强得多数据透明、可复现。我前阵子做整机性能回归就是用 Perfetto 的 SQL 批量导出指标一周下来省了至少三个工作日的统计时间。第四遇到问题时多查官方文档或直接看 UI 里的示例 trace。Perfetto 官方示例库里提供了各种场景的 trace 文件比如典型掉帧、内存泄漏、进程被杀死等。下载一个导入 UI对照官方标注学习比自己瞎抓 100 次都有用。最后说点接地气的。真正让我长期把 Perfetto 当成主力工具的原因其实就是它的“可量化”和“可复现”。多抓两次、多刷几遍 SQL就比用 systrace 时肉眼猜要靠谱很多。如果你也是性能优化刚入门建议从最最基础的一条命令开始抓一次启动 trace把 UI 读一遍再试着自己写一条 SQL 找最耗时的 slice一轮下来基本就能明白它到底是怎么帮你省时间的。