
1. 从Systrace到Perfetto为什么现在的性能排查都离不开它先说一个我自己的真实经历。之前接了一个线上反馈说是App首页偶尔卡顿但开发同学用Android Studio自带的CPU Profiler反复复现进程CPU占用率看着完全正常方法耗时也都是毫秒级问题始终抓不到。后来我让测试同学用Perfetto抓了一份10秒的trace对着UI线程附近的CPU调度一看问题根本不在App本身——是系统有两个后台进程在频繁抢占CPU导致我们的UI线程虽然没跑耗时任务却在不停地等时间片。这个案例基本能说明Perfetto的定位了。它不是一个简单的方法耗时分析工具而是一个系统级追踪分析平台能把应用内部行为、CPU调度、内存分配、I/O、网络、内核事件全部放到同一条时间轴上对比。它由Google在2018年前后推出是Systrace的正统继任者底层基于Linux内核的ftrace机制采集到的数据以protobuf格式序列化最终通过Web UIui.perfetto.dev可视化。对大多数Android开发者来说Perfetto最核心的使用场景有三个启动耗时排查、UI掉帧归因、后台任务与系统负载分析。它比传统工具强的地方在于你不仅能看到自己的方法走了多少毫秒还能看到这期间系统在内核层面的调度、CPU核心有没有偷懒、线程状态是不是在等锁等I/O而这些恰恰是很多疑难卡顿的真正来源。文章面向的是刚接触Perfetto或只会点Record按钮的同学。我会尽量把抓取、读取、分析这条链路讲完整同时给出一些文档里不太会写、但实战中很有用的经验。如果你手头有Android 9以上、最好是10以上的设备可以跟着操作一遍效果比干读好太多。2. 抓取一份能说明问题的trace两种主流姿势2.1 网页端实时录制最推荐的入门方式Perfetto的Web UIui.perfetto.dev不只是用来打开trace文件的它本身就是一个录制客户端。在左侧菜单点Record new trace连接上开启了USB调试的安卓设备就能开始配置。录制配置界面里需要重点关注几个选项Buffer size缓冲区大小默认的64MB可能不够。录制内容越多数据量越大如果buffer满了且没有及时拉取早期数据会被覆盖。建议系统级分析开到256MB以上纯App分析128MB足够。Duration时长问题不能稳定复现时建议30秒以上能稳定复现的画面卡顿10秒也够用。Trace categories数据类别这是最关键的一步。很多新手直接点Start Recording抓到的东西要么缺少目标数据要么文件巨大难处理。常用组合我拉一个清单排查目标建议勾选的数据类别UI掉帧/卡顿Frame timeline帧绘制时间线、Scheduling details调度细节、CPU frequencyApp启动流程App activitiesActivity生命周期、Binder driver、Scheduling detailsCPU占用异常CPU frequency、System callssyscall、Scheduling details内存问题Memory / heap profiler需进程支持I/O问题Disk I/O、F2FS、Linux file system说到底你是在回答一个问题我怀疑的瓶颈可能出现在哪个层就采集哪一层的证据。什么都勾的结果就是文件很大、分析时噪音很多。录制界面右上角会看到设备列表和Start recording按钮。点完之后界面会停在录制倒计时结束后会自动拉起trace文件选择open with viewer即可。2.2 命令行抓取适合无人值守与自动化场景在无法使用Web UI的场合比如跑自动化测试、真机批量采集命令行是更可靠的方案。基础命令如下adb shell perfetto -o /data/misc/perfetto-traces/trace.pb -t 15s -b 256mb sched freq idle am_proc_start am_proc_destroy参数拆开看-o输出文件路径这个路径需要位于/data/misc/perfetto-traces/目录普通应用目录是没有写权限的。-t录制时长单位秒15秒适合大多数场景。-bbuffer大小256MB适合多类别采集。后面的sched freq idle am_proc_start am_proc_destroy是category列表你可以理解为web端勾选的数据类别。把上面命令跑完需要把trace从设备拉回来adb pull /data/misc/perfetto-traces/trace.pb .如果是通过USB连接还可以直接使用-c指定一个配置文件把类别、buffer、时长等内容都写进config方便团队复用。perfetto自带完整的config格式说明实践中我更建议用命令行的方式先跑通再往自动化框架里集成。2.3 录制时容易被忽略的两个细节第一先解锁屏幕再录制。息屏状态下系统会进入深度睡眠帧率和调度信息完全不典型抓出来的trace基本没有参考价值。第二尽量明确问题时段。有时候trace长达30秒页面就卡了那么一瞬间如果只盯着整条时间轴看会非常累。你可以在trace上添加标记marker或者在录制前先让操作者做一个明显动作比如连点屏幕5下之后再对着这个时间点找问题。命令行录制时也能在代码里调用Trace.instant()写标记这个能力在分析自动化流程里的偶发问题特别有用。3. Perfetto UI从文件到结论的桥3.1 打开trace文件之前要知道的事无论用哪种方式抓回来的.pb文件都需要用ui.perfetto.dev打开。有人问我为什么Android Studio打不开这个格式——因为Perfetto的pb文件是protobuf序列化的Studio内嵌的Profiler读取的是另一种数据流。这个差异其实也反映了Perfetto的设计思路它被设计成一个跨平台的开放格式不只是给Android Studio服务的所以打开和解析都通过Web UI完成。打开之后第一眼会看到若干横向的轨道Track它们自上而下大致是Overview概览、CPU切片调度、帧时间线、进程/线程详细信息、logcat日志等。初次打开会有点眼花但读trace是有套路的建议按照下面这个顺序来看。3.2 Overview区与CPU调度切片顶部概览区域展示的是trace的整体信息包括总时长、各进程的CPU占用粗略曲线。看到一个很高的CPU占用波峰时先不要急着定位进程往下的Track选择区会有更细的信息。真正干活的是CPU切片区。每个CPU核心是一行track横轴是时间纵轴是这个核上跑的线程。每个彩色块代表某个线程占用该核心的时间段。这一层能回答三个问题UI线程在问题时间点在哪个CPU上运行跑了多久有没有其他线程抢占了CPU导致UI线程被调度走CPU频率是否被限制thermal或者省电策略。这里最常用的交互是点击track上的任意线程块下方会高亮这个线程的完整生命周期右键能查看进程详情包括线程状态与唤醒链。3.3 Frame Timeline掉帧信号灯如果你勾选了Frame timeline类别UI里会出现一个很直观的信号灯区域。每一行是一个渲染帧绿色表示该帧在约定时间内绘制完成红色则意味着超时。对这个区域要建立两个概念帧时间一个帧从提交到渲染完成的时间超过16.6ms60Hz刷新率就是掉帧信号。相邻帧间隔即便单个帧没超时如果帧与帧之间间隔抖动明显画面观感依然会卡。Perfetto的时间轴能精确到微秒你完全可以放大到两个帧之间去观察间隔。在Frame Timeline的track上横向拖动能对齐看一下当时UI线程在做什么。掉帧时UI线程往往被别的slice占据——比如主线程在处理一个耗时网络的回调、在做大Bitmap的decode、或者在等待Binder调用返回。3.4 线程状态与Slices定位代码路径Perfetto UI里每种线程状态有固定颜色标识绿色Running正在CPU上执行蓝色Runnable可以运行但没拿到时间片橙色Sleep休眠/等待唤醒紫色/灰色Uninterruptible不可中断的睡眠通常意味着I/O在等待白色Blocked阻塞等待锁或者Binder。五花八门的进程切片Slices则是日志级别的函数名和事件名比如performTraversals、ActivityThread.handleResumeActivity、Choreographer#doFrame。把线程状态和slices放在一起看能形成一个完整链条UI线程先进入Sleep旁边某线程在做GC导致所有线程被stop the worldGC结束后UI线程恢复Running中间空档就是卡顿的一帧。3.5 用SQL查询在trace里检索证据Perfetto还内置了一个SQL引擎Trace Processor可以在界面的Query查询标签页跑SQL这对大型trace来说简直是利器。比如我想看所有onCreate方法的调用时长select name, dur, cpu, utid from slice where name onCreate结果会直接在下方表格里展示。再比如我想看主线程上所有耗时超过100ms的sliceselect s.name, s.dur/1e6 as ms, s.ts/1e6 as time_ms, t.name as thread from slice s left join thread_track tt on s.track_id tt.id left join thread t on tt.utid t.utid where dur 100000000 order by dur desc这个能力在你面对一个2GB大trace、手动翻找完全不现实的时候几乎是唯一高效路径。Web UI的Query面板底下就是SQL编辑器语法属于SQLite学习成本不高。4. 三类高频问题的trace分析实战4.1 启动慢从进程创建到首帧的时间账App启动优化的第一件事往往是先看时间都花在了哪里。Perfetto能精确地把启动过程拆成几大块进程创建与预加载、Application初始化、Activity创建与首帧渲染。实际操作时我会做这样几步在顶部时间轴上找到App进程一般名字是包名或用[package]/...标识选中Application和Activity相关的Track。用放大镜把从进程创建到首帧出现的时间段拉出来然后逐个看大块slice的耗时。几个重点slice名称我列出来ActivityThread.handleBindApplicationApplication启动的入口ContentProvider.onCreate所有ContentProvider的初始化这是隐藏耗时大户ActivityThread.performLaunchActivityActivity对象创建ActivityThread.handleResumeActivityonResume后的流程Choreographer#doFrame真正向系统请求绘制的一帧启动时间过长时往往是某个ContentProvider的onCreate里做了IO或者Application的attachBaseContext里做了太多反射。在trace里你看到哪个slice的宽度明显超出预期顺着slice去找对应代码一找一个准。另外有个小技巧在trace顶部的Search搜索输入FirstFrame或者reportFullyDrawn能直接定位到首帧相关marker减少手动查找时间。4.2 UI掉帧往下钻取到被抢的真相掉帧问题最常见的两种假象假象一主线程方法的耗时看起来都很短但掉帧依然发生。这种情况在trace里表现为UI线程的slice本身不长但Frame Timeline是红的。观察UI线程的Thread State你会发现它很长一段时间是Runnable蓝色而非Running绿色——说明它一直在等时间片。谁抢了它去CPU调度区看相同时间片可能是JIT编译线程、GC线程或者其他进程的渲染线程把核心占满了。此时问题方向不是优化App方法而是降低整体CPU负载或者调整线程优先级。假象二掉帧瞬间主线程确实在忙但忙的点出乎意料。比如有个掉帧CPU slice显示主线程在跑大段memcpy或memset这和业务代码没有直接关系通常是系统在做内存压缩或文件拷贝。翻一翻其他进程会发现后台正在解压资源或执行dex优化。这类问题要用Perfetto看到横向证据链才能定位单看App自身代码根本摸不着头脑。排查时要习惯这个动作点掉帧的红色帧看此时UI线程在什么状态再看同一时间其他Tracks里在发生什么。把这两个信息放进同一时间坐标系基本能区分是我们写了慢代码还是环境拖慢了我们的代码。4.3 后台任务导致CPU占用异常有段时间线上反馈某个版本升级后手机发热明显。我抓了同场景下新老版本的trace对比发现新版本多了一个周期性任务每次启动都会唤醒一个线程去做整包压缩计算持续占用CPU 4%~8%。这个线程的slice宽度和频率在trace里看起来非常清楚对照业务代码反查是一个取巧的缓存加密逻辑本来只想在启动时跑一次但因为线程池没复用变成了每个页面都触发的定时任务。这类问题的排查思路是把trace里的CPU占用按进程排序找到异常进程再进入该进程的线程列表看哪个线程slice最宽、最频繁。Perfetto UI的CPU track区域支持按进程进行颜色高亮用起来非常直观。说到SQL就是用select utid, sum(dur) from sched group by utid order by sum desc limit 10直接按CPU执行总时长排一下高占用线程无所遁形。5. 抓trace和分析过程中的高频坑5.1 命令找不到或者权限不足adb shell perfetto报command not found时先检查设备系统版本和厂商ROM。部分Android 9设备未启用Perfetto需要先在系统设置中打开开发者选项里的系统追踪开关厂商叫法可能不同比如System Tracing。权限层面普通应用工程师的设备如果没root走Web UI或者命令行都行不需要root真正需要root的是深入内核事件采集日常开发用不到。5.2 trace文件打不开或数据量异常小打不开最常见的原因是录制时buffer太小且时长太短数据不完整。另外-t参数给的单位是秒我曾经在CI脚本里写过-t 1000以为是一千毫秒结果实际跑了1000秒直接把测试机存储塞满了。Perfetto的时间单位没有歧义空间务必在配置里写清楚。数据量异常小通常是录制的时段里设备进入了休眠或者抓的全是空跑页面建议在录制期间做真实操作并让设备保持亮屏。5.3 时间刻度怎么换算Perfetto UI里显示的时间戳单位是纳秒ns我第一次看的时候觉得数字太长了后来习惯了用ts/1e6换算成毫秒值。在SQL里做分析时也要时刻清楚单位的换算1e6表示毫秒1e9表示秒slice的dur字段单位是纳秒除以1e6之后够直观。5.4 分析时最忌讳的一件事不要在trace里无目的地乱翻。非得说一个实战建议的话那就是每一次看trace之前先写下一句话问题描述比如首屏广告页的加载过程中UI线程在哪个时间点等待超过了300ms。有了这句话再决定去看哪个track、哪些线程、哪些slice读trace的效率会立刻提上来。还有一个容易被遗忘的点不要只抓单次trace。偶发问题抓三次每次记录当时的系统负载比如是否在充电、是否开了多个大应用对比三次结果的共性才更容易刨到根因。一次trace只能证明发生了这件事三次trace才能告诉你这件事是不是常态。6. 进阶有SQL和自定义Track之后Perfetto还能做什么如果只把Perfetto当可视化工具用起来其实浪费了它一半的价值。真正让它能处理亿级事件而不崩溃的是底层的Trace Processor也就是那个内置的SQL查询引擎。举个例子。你需要定位用户从点击到首帧绘制经历了哪些步骤可以写一段SQL按时间顺序把所有跟Input和Frame相关的slice列出来。这种组合查询能让trace分析从看热闹变成查底细。很多性能监控平台后端根本的做法就是把Perfetto导出的数据喂给Trace Processor定时批量分析跑出自动化报告。自定义TrackCustom Track则是做针对性可视化的关键。你的App可以按自己的逻辑往trace里写事件比如网络库把每次请求的开始、结束、重试都写入自定义track再看UI线程同时段的帧时间一眼就知道网络框架的耦合对掉帧的影响。这个能力在Android的systrace时代就存在Perfetto沿用了并强化了。附带说一个经验trace分析不能只靠Perfetto一个工具闭环。Perfetto负责系统全貌method profiling工具负责具体方法内部的热点分布两者结合才是完整的性能工作流。经常有同学在Perfetto里发现主线程有耗时slice但不知道slice对应的代码在哪一行这时再开Android Studio的CPU Profiler对同一段操作重新profile一次双份证据互相印证才敢改动代码。7. 关于Perfetto版本和未来适配的碎碎念Perfetto更新迭代速度很快Google在持续把Android的一些性能基础设施迁移到Perfetto上比如Power Rails、Thermal、网络等子系统也逐步有了trace支持。工具本身是开源的这意味着它不会像某些闭源分析工具那样某个版本之后就停止维护。对团队来说把Perfetto抓trace的配置沉淀成内部文档、把常见的分析SQL做成脚本库价值比临时抱佛脚大得多。我给团队做的trace_analysis.sql里目前维护了大约30条常用查询从Top CPU线程到掉帧窗口内的Binder调用都是日常反复用到的。毕竟性能分析的核心不是你会不会点按钮而是能不能快速从一堆数据里找到那条异常链路SQL和自定义Track就是帮你建立这条链路的脚手架。8. 最后分享一点实际心得我自己用Perfetto这几年最大的感受是性能分析里最难的部分不是工具操作而是你怀疑的方向是否正确。Perfetto最大的价值恰恰是能快速验证猜测——你说启动慢是IO导致的它就真的放一条IO统计给你看你说网络回调阻塞了主线程它就把Binder唤醒链拉出来打你脸。所以真到了排查疑难杂症的时候我不会在一开始就开一堆Profiler而是先随手抓一份Perfetto trace跑一跑基础SQL把大方向定下来再针对性地上手段。这套流程我沿用很久每次都能少走很多弯路。如果你刚接触Perfetto建议先抓几次自己App冷启动和日常操作的trace对着上面的章节把启动时间、CPU调度、Frame Timeline都看一遍。工具不上手练光看文章记再多的命令和参数到现场还是会手忙脚乱。等到你真的用它定位过一次隐藏很深的卡顿我觉得你大概率会跟我一样把Perfetto放进日常开发的必备工具列表里。