资讯详情

用perfetto精准定位Android卡顿根因的实战指南

📅 2026/10/4 1:35:03 | 华诺云谱 👁 阅读
用perfetto精准定位Android卡顿根因的实战指南
做Android性能优化这几年如果让我只留一个分析工具我会毫不犹豫选perfetto。早年大家习惯用systraceAndroid Studio的profiler出来后又改用profiler但真正遇到线上反馈、竞品对比、偶发卡顿这类问题时systrace的局限一下就暴露了buffer太小、数据源太散、分析器太弱。perfetto作为systrace的完整继任者从数据采集、文件格式到分析UI几乎把所有痛点都解决了。这篇文章不讲PPT概念就从一个实战案例说起一个在用户反馈里被说“滑动卡成PPT”的页面我是怎么用perfetto一步步定位到根因的。很多人对perfetto的第一反应是“不就是systrace换了个皮嘛”这句话大错特错。我一开始也以为只是一个新的trace工具直到有一次遇到一个偶现卡顿systrace反复抓了十几次都看不出来最后是perfetto的长时抓取加SQL分析炸出来的。从那时起我就下了个结论新项目里再遇到卡毒我只信perfetto。下面把完整思路和实战技巧都摊开讲希望大家少走点弯路。1. 先搞清楚perfetto和systrace的关系1.1 从systrace到perfetto为什么老司机也开始切新工具systrace在Android 4.x时代几乎是做性能分析唯一靠谱的武器原理也不复杂通过atrace把内核的ftrace和Android用户的trace收集起来然后渲染成一个HTML页面。我现在还记得打开一个几秒钟的systrace浏览器要卡半天放大缩小全靠缘分想查一段具体的代码耗时只能靠肉眼硬看。到了Android 10之后Google开始主推perfetto底层完全重写文件格式改成protobuf数据源也从ftrace/atrace扩展到了heap profiling、network、battery这些。直观对比看这张表对比项systraceperfetto数据格式HTML/ctraceprotobuf.perfetto-trace抓取时长通常几秒到十几秒buffer小易丢数据可长时间buffer可控到一个G数据源ftrace/atrace为主ftrace/atrace/heapprof/network/battery等分析UI网页版卡顿明显Perfetto UI缩放拖拽流畅扩展性弱基本只能肉眼看支持SQL查询、自定义metric、插件自定义应用traceTraceCompat/Trace.beginSection完全兼容还支持instant、async事件为什么老司机愿意切核心就两个理由一是perfetto能抓到systrace抓不到的东西比如更细的CPU调度状态、线程优先级、锁竞争、Binder调用细节二是perfetto的大文件处理能力明显强UI响应快Zoom到某一段能精确到微秒级。对于卡顿分析来说这俩刚需所以只要手里有Android 10以上的机器我基本不再碰systrace。1.2 核心概念Trace、Slice、Counter与Track先说几个术语后面分析会一直用。perfetto里的基本单位不是一个点而是一条横向的泳道官方叫Track。Track上放的是两类东西Slice和Counter。Slice是一条有开始时间和结束时间的横条代表某一段动作从什么时候开始到什么时候结束。最常见的就是主线程上的Choreographer#doFrame它就是一个Slice。Counter是一条随时间变化的数值曲线典型代表是CPU频率、内存占用。Slice能告诉你“干了多久”Counter能告诉你“资源在什么水平”两者配合才能判断卡顿是业务耗时、CPU欠频还是GPU瓶颈。还有一个不能忽略的是进程和线程的层级关系。打开trace后最外层是进程第二层是该进程下的线程。因为我们日常说的“主线程卡了”在perfetto里通常就是指某个进程下名为“Main”或“主线程”的Track上出现了超长Slice或者是该线程长时间处于Running/Runnable状态但没干活。搞懂Track的组织方式后面定位问题至少不会迷路。1.3 perfetto的数据来源它不只是一个traceview有人以为perfetto和SDK里的Debug.startMethodTracing一样只能记录Java方法调用那就太低估它了。perfetto的数据源可以按需开启最常用的是sched、freq、idle、binder_driver、gfx、view、audio、video、surfaceflinger这些。sched记录线程调度切换能看到每个线程Running/Runnable/Sleeping的时间片freqCPU频率变化结合sched可以看到是否因为降频导致线程没有被及时调度idleCPU空闲状态分析功耗和调度延迟时很有用binder_driver记录Binder调用发起端和执行端卡在Binder上时能看出谁在阻塞谁gfx/viewAndroid渲染和View绘制相关的trace点比如doFrame、performTraversalsvideo/audio媒体管线相关。这样一说就明白perfetto不是方法级profiler它是一个全系统级trace系统。卡顿往往是系统资源调度和业务逻辑一起造成的结果只看app自己那点地方永远找不到真相。这也是“perfettosystrace”这类工具和Android Studio CPU Profiler最大的区别。2. 卡顿分析准备如何抓到一份有价值的trace2.1 三种采集方式怎么选抓trace的方式主要分三种命令行抓取、Perfetto UI网页抓取、代码内抓取。没有绝对最优只有适不适合当前场景。命令行抓取是绝大多数情况下的首选。连接真机打开adb shell执行一句perfetto命令就行。优点是可控性强想抓多久、多大buffer、开哪些数据源全由你来定。遇到有复现步骤的卡顿一般就靠这种方式抓现场。Perfetto UI网页抓取适合遇到不熟悉命令行的同学。打开ui.perfetto.dev右侧点Record选好设备、数据源和时长然后点录制perfetto会在设备侧自动下命令结束后UI自动加载。这种方式的好处是可视化配置不用记参数缺点是自动开启的数据源有限很多时候不够细。代码内抓取适合线上用户反馈或者复现步骤很长的场景。你在应用里集成Perfetto SDK指定一个触发条件比如检测到主线程掉帧超过某个阈值时自动开始抓trace并保存文件上传。这样拿到的是用户真实环境里的卡顿现场比本地模拟复现靠谱得多。缺点是集成成本但一旦跑通性价比非常高。2.2 perfetto的获取方式与版本选择说到perfetto下载这里稍微展开一下。如果你只是想用官方UI那根本不用下载任何东西浏览器直接打开ui.perfetto.dev就行。如果你要用命令行抓trace两种方式第一使用设备系统自带的perfetto。Android 10及以上系统出厂通常已经内置了/system/bin/perfetto直接adb shell perfetto就能跑。但系统自带的版本可能偏旧某些新数据源不支持。第二手动下载perfetto二进制包。perfetto官网release页面提供了perfetto和tracebox的压缩包根据自己的开发机平台下载对应版本。下载后解压把perfetto文件用adb push到设备/data/local/tmp/目录下然后chmod x再执行。我个人习惯在本地维护一份最新版本的工具遇到系统自带包不给力时就push自己这份上去基本没有兼容性问题。这里有个细节下载时尽量选择带tracebox的包。tracebox是一个自包含的trace工具既能在设备端采集也能在开发机上做解析。比单独一个perfetto二进制更全面。另外提醒一句perfetto是持续迭代的开源项目旧版本可能对最新trace格式解析不完整所以尽量保持工具版本和trace processor版本同步不然打开文件会提示格式不兼容。2.3 常用命令行参数实战命令行抓trace的标准姿势我一般这样写adb shell perfetto -o /data/misc/perfetto-traces/trace.perfetto-trace -t 10s -b 32mb sched freq idle binder_driver gfx view这句命令的意思很简单输出到/data/misc/perfetto-traces/trace.perfetto-trace路径下抓10秒buffer给32MB数据源包含sched、freq、idle、binder_driver、gfx、view。抓完以后用adb pull把文件拉回开发机再用UI打开。-o指定输出文件这里有个容易踩的坑普通应用没法直接写到/data/misc/perfetto-traces/这个目录建议用/data/local/tmp/这样的shell可写路径抓完再拉回。-t是抓取时长卡顿复现一般10~30秒就够太短可能漏掉太长文件会爆炸。-b是buffer大小我建议从32MB起步数据源一多buffer太小会丢掉最早的数据宁可多给一点。如果你想要更精细的数据可以加-c指定一个配置文件把哪些进程、哪些线程要单独追踪写清楚。比如我想重点关注微信和系统UIbuffers { size_kb: 65536 } data_sources { config { name: linux.ftrace target_buffer: 0 ftrace_config { ftrace_events: sched/sched_switch ftrace_events: power/cpu_frequency ftrace_events: binder/binder_transaction } } } data_sources { config { name: android.atrace atrace_config { categories: gfx categories: view app: com.example.target } } }配置文件的好处是可以精准圈定要追踪的进程或线程减少无用数据文件更小分析也更聚焦。对于只有几MB的小traceUI打开和操作的流畅度都不一样所以别小看这个设计。2.4 抓取现场的几条铁律抓卡顿trace看着简单其实很多细节决定成败。我总结了几条铁律第一权限要够。线上release包如果不可调试atrace可能拿不到应用自定义的trace点这时候不是perfetto没用而是普通调试权限受限。要么使用userdebug系统要么集成Perfetto SDK从应用内部发起抓取。第二复现路径要清晰。抓trace前最好先把卡顿的触发路径走一遍确定步骤然后在第N步开始前启动perfetto。否则干等卡顿buffer很快就被无关信息填满。第三时间戳要统一。perfetto默认用boot clocklogcat用的是realtime两者对不上就会导致“明明卡顿了但trace里看不出来”的尴尬。抓取完后如果要对齐logcat可以用-c配置里设置clock或者在UI里切换clock domain这点后面排错部分再细说。第四不要一上来就抓全数据源。全开一时爽分析火葬场。数据源越全文件越大反而把关键痕迹淹没在噪声里。我一般第一遍只开gfx、view、sched、freq这几个定位到方向后再针对性地补数据源。3. 实战定位卡顿根因的四步法3.1 用Perfetto UI打开trace先看哪几条轨道拿到trace后我习惯打开Perfetto UI文件拖进去等待加载完。UI默认会铺开很多Track但一上来不能瞎看要有优先级。第一优先看“Frame Timeline”或者叫“Frame Displayed”这类轨道。perfetto里会有每条frame是否被掉帧的标记如果看到一条红色竖线或者“Janky”标记基本可以认定这里就是卡顿发生点。这条轨道能看到frame从app绘制到surfaceflinger合成的完整生命周期只要某一阶段超时它就会亮红灯。第二优先看目标进程下的主线程Track。Android UI线程通常显示为“Main”或者“Thread-2”在perfetto里可以通过搜索Choreographer#doFrame来找主线程上的关键slice。卡顿发生时这个区域往往会出现明显断口或者长条blocking。第三看RenderThread。渲染线程叫RenderThread里面重点找DrawFrames、syncFrameState、flushCommands这些slice。如果app业务线程没大问题但RenderThread一直忙就要往渲染方向查。第四看SurfaceFlinger进程。掉帧除了app侧绘制得慢还有可能是SurfaceFlinger合成得慢。重点关注Display-0、BlastBufferQueue这些Track能看出是vsync延迟还是合成任务堆积。按照这个顺序基本能判断卡顿发生在“业务执行—UI绘制—渲染线程—系统合成”哪一段再做下一步深度分析。3.2 用SQL把问题区间“切”出来perfetto比systrace强的一点是内置了trace processor可以直接用SQL查trace内容。虽然UI上拖拽也能看到大概但数据一多、时间一长就靠SQL来精确锁定关键区间而且适合写脚本批量分析。我最常用的几个查询找出主线程上耗时超过100ms的sliceSELECT ts, dur, name, tid, thread_name FROM slice WHERE thread_name Main AND dur 100000000 ORDER BY dur DESC;注意dur单位是纳秒100000000是100ms筛出来以后就能看到主线程在卡顿期间到底在干嘛。查某个时间段内目标进程主线程的CPU状态分布SELECT state, SUM(dur) AS total_dur FROM thread_state WHERE utid ( SELECT utid FROM thread WHERE name Main AND pid target_pid ) AND ts 开始时间 AND ts 结束时间 GROUP BY state;这里的state会有Running、Runnable、Sleeping、Blocked等。如果Runnable时间很长说明线程想跑但没分到CPU可能要排查优先级、核数、负载如果Blocked时间很长说明线程在等锁、等IO、等Binder。找出某个时间窗口内你关注进程里所有slice的耗时Top10SELECT process.pid, thread.name AS thread_name, slice.name, SUM(slice.dur) AS total_dur FROM slice JOIN thread USING(utid) JOIN process USING(upid) WHERE process.pid 目标pid AND ts 开始时间 AND ts 结束时间 GROUP BY slice.name ORDER BY total_dur DESC LIMIT 10;这个查询特别适合卡顿范围比较宽的时候能一眼看出到底是哪个方法占用了主线程大量时间。用SQL的好处在于客观。通过肉眼在UI里拖经常会被视觉上的大色块带偏但其实那个色块可能是同一重复操作的堆叠。SQL直接按总耗时聚合一行数字摆出来问题焦点一下子就清晰了。3.3 掉帧背后的Choreographer机制要定位卡顿就必须理解掉帧是怎么产生的。Android的界面刷新由Choreographer驱动每收到一次vsync信号就会回调Choreographer#doFrame这个方法里会依次处理Input、Animation、Traversalmeasure/layout/draw最后把DisplayList提交给RenderThread。如果doFrame里的内容处理超时或者doFrame整体延迟就会错过这个vsync周期表现为掉帧。在perfetto的trace里你可以直接在主线程Track上搜索Choreographer#doFrame这个slice。正常情况下它的duration应该很短甚至合并到整体帧链路里看不出来。如果卡的严重会发现doFrame的开始时间比vsync信号要晚很多或者doFrame内部某一段比如performTraversals特别长。这里有个容易犯的错误看到主线程有一段很长的方法slice就说是卡顿元凶但这个slice可能只是被系统调度延迟了实际并没有占用CPU。所以必须结合thread_state看这段slice期间线程是真在Running还是Runnable在等CPU还是Blocked在等锁。后面我会专门讲怎么验证。3.4 案例一主线程耗时方法之前优化某个信息流页面时用户反馈滑动到某一屏会卡顿。我抓了一段perfetto trace在Frame Timeline里看到明显的jank然后SQL查了主线程耗时Top slice排在最前面的是一个自定义业务方法BannerSDK.onLayout单次耗时接近180ms而且出现了多次。点开这个slice发现它发生在performTraversals内部说明页面在布局阶段做了大量重活。再展开调用栈原来这个Banner组件一次layout往ViewGroup里add了几十个Drawable每个Drawable都有自己的bound计算和状态更新。这种问题在trace里看就是layout阶段一个宽大的色块把doFrame整体拖垮了。解决方式很直接把Banner里的图片资源改为分层复用使用一个View统一绘制减少布局节点和Drawable数量同时限制Banner刷新频率避免每帧都触发requestLayout。改完后再抓tracedoFrame从原来的几十毫秒降到了3ms左右Frame Timeline一路绿色卡顿消失。从这个案例可以总结一个经验trace里的“长条”不一定就是业务方法本身垃圾但它一定是入口。顺着长条往里点开看调用栈下方的函数调用往往能顺藤摸瓜找到真正拖慢布局或者测量的元凶。3.5 案例二Binder调用堵塞另一个更隐蔽的卡顿案例是Binder阻塞。那次是某个页面点击后要等一两秒才能跳转一开始我怀疑是网络请求慢但trace看完发现主线程根本没有在做网络操作而是阻塞在了一个叫binder transaction的地方。在perfetto里开启binder_driver数据源后主线程Track上能看到一个黄色或蓝色的slice名字类似binder transaction旁边还带有对方进程和线程的信息。SQL进一步查binder的binder_transaction和binder_transaction_received发现等待方主线程blocking在system_server里的某个慢服务上慢服务里执行的是一个取WIFI列表的同步IPC。这类问题跟CPU耗时不一样主线程可能一直Sleepingthread_state显示Blocked但它确实是在等一个远端结果一旦远端服务响应慢卡顿就出来了。解决办法是改异步将IPC搬到子线程或者改为缓存机制把WIFI列表结果提前拉到内存UI线程只从内存读取不再每次同步调用。改完后再抓同样的场景trace里Binder阻塞的slice彻底消失。Binder问题很考验经验因为它在trace里不像CPU耗时那么直观。很多新手看到主线程一大片sleep就直接跳过其实恰恰是这种等锁、等IPC的卡顿在perfetto里需要用binder和lock这俩数据源才能看得清楚。3.6 案例三渲染线程GPU瓶颈前两个问题都在Android应用层渲染卡顿还有一种典型场景主线程的doFrame挺快但RenderThread和GPU跟不上。那次是在壁纸视效页面滑动时掉帧严重。trace里主线程doFrame只有5ms但RenderThread的DrawFramesslice特别长且一段又一段地堆积明显是渲染跟不上主线程下发的帧率。打开GPU相关Track后看到EglSwapBuffers或者vkQueueSubmit的间隔不均匀也有一系列很宽的blocking。根因后来定位到页面用了整屏的毛玻璃模糊效果GPU load太重单帧渲染在低端机上经常超过17ms。我们做了两个优化把毛玻璃改为预计算好的一张静态模糊底图只在内容变化时重新模糊同时把整个壁纸相关View用setRenderEffect换成硬件层如果版本支持减少重复绘制的GPU开销。优化后RenderThread里的长slice明显变短低端机滑动也恢复流畅。渲染线程瓶颈在perfetto里看的是RenderThread和GPU的配合如果RenderThread空闲时间很少但GPU忙不过来通常是shader太复杂、过度绘制、或者大纹理上传需要结合渲染相关的底层trace点做判断。4. 常见问题与排查技巧实录4.1 trace文件过大UI卡死怎么办大的trace文件用UI打开确实会卡几百MB的trace在低配开发机上一加载就是两三分钟放大缩小还掉帧。我的做法有两个方向。第一源头控制。抓取前不要什么数据源都开日常卡顿分析只需sched freq binder_driver gfx view这五类Buffer给32~64MB时长控制在15秒内一般也就几十MBUI打开毫无压力。如果是在用户现场抓trace建议用配置文件只关注目标app和system_server数据量会小很多。第二分析时拆小。已经抓到的大trace不要直接在UI里硬刚先用trace_processor命令行把关键数据SQL查询出来比如统计Top slice、掉帧区间、线程状态分布把结论拿到后再用UI只定位到局部时间段展示。UI里还可以勾选“只显示涉及track”或者直接把无关Track隐藏操作起来会轻快不少。4.2 抓取时trace中只有系统看不到应用线程这种情况很常见尤其是在线上release包上。可能原因有几个应用没有开启android:debuggable导致atrace无法给应用注入自定义trace点。这是最主要的坑解决办法是集成Perfetto SDK在应用内部发起抓取而不是用adb从外面抓。 -线程名不是标准的“main”或者线程名太长被截断。用SQL按pid过滤时最好只看pid别只看线程名。应用确实创建了线程但perfetto没开启sched_switch看不到调度事件。确认数据源列表里有没有sched如果没有加上sched再抓一次。遇到看不到应用线程的情况不要急着把锅甩给perfetto先检查权限和数据源。大多数时候是抓取方式的问题。4.3 如何确认某段CPU时间确实是卡顿元凶这是分析中最考验经验的地方。一个slice很长不代表它就是元凶。举个例子你在trace里看到主线程有个100ms的slice叫wait看起来像是它在等但你只能确定线程在block不能确定是谁导致它等的。这时候要看两条线索。一条线索是线程状态。切换工作区线程状态视图如果该slice期间线程状态是Runnable但等待时间却很长说明它想跑但CPU没分给它需要去看当前系统CPU负载、有没有高优先级线程抢占。如果线程状态是Sleeping说明它主动等某件事继续去查它在等什么锁、等哪个IPC、等哪个IO。另一条线索是trace里的事件配对。比如Binder调用中的binder_transaction和binder_transaction_received之间总有对应关系锁等待可以通过futex相关的trace来看谁在持有锁。只有把阻塞链条补完整才能确认元凶不是眼前这个slice而是背后的调度、锁竞争或者服务端慢。4.4 perfetto的SQL和metrics让报告更高效前面已经提过SQL这里我想再补充它对于团队协作的价值。每次优化完我习惯用trace_processor把关键数据抽出来生成一个简短的SQL结果配上UI里的Time Range截图扔到工单里。这样整个团队的同事不用都学会UI操作也能看懂卡顿根因。perfetto还支持自定义metrics格式是.sql文件里面可以定义一整套统计指标。比如我们团队制作过一个“主线程长耗时Top N”的metric每次拿到trace后跑一下自动输出哪几个方法超了100ms聚合出平均耗时、最大耗时、次数非常省人工。这东西写一次就能一直用强烈建议有精力的话搞一套。4.5 和其他工具配合使用的技巧perfetto不是万能的它擅长的是系统级trace但有些问题需要别的工具把拼图补全。如果发现卡顿集中在方法耗时上可以用Android Studio CPU Profiler或者simpleperf做Native层热点采样确认具体函数如果怀疑内存原因导致GC频繁配合Memory Profiler或者heapprofd看堆内存如果需要录屏还原用户在卡顿前做了什么操作用adb screenrecord录一段视频然后对比trace时间戳和视频帧能快速确认复现路径logcat里如果有Skipped 32 frames或者Input event took too long这类输出能辅助定位perfetto分析方向。工具链搭配好了perfetto才能发挥最大威力。我个人的习惯是先用perfetto看“宏观场景”再用simpleperf或Memory Profiler查“微观代码”从系统调度到App实现层层下钻。5. 写在最后我的一点实战心得玩perfetto这么多年最大的感受是它不难学但容易让人“瞎”。很多开发者下载下来打开UI看到满屏的Track不知道从哪下手最后草草截个图发群里说“看起来主线程确实很忙”。这里没有捷径就是按我在第三节分享的四步法一步一步来先看Frame Timeline确认卡顿点再看主线程、RenderThread、SurfaceFlinger最后用SQL把疑问变成数字逐个排除。工具本身一直在更新我也时不时去perfetto官网看看changelog有几个功能是真香比如直接录制Trace并保存为protobuf格式、用trace_processor做自动化分析、自定义metric。早几年用systrace根本不敢想这些事。而现在还有个方便的点下载新版perfetto后还要记得保持设备端二进制和trace processor版本同步否则某个新数据源可能解析不出来这个坑我踩过不止一次。最后再分享一个个人习惯分析卡顿时我永远会用一支虚拟笔把可疑Slice的起止时间在Trace上下划一道然后下意识地问自己三个问题——这个slice期间线程在Running吗它阻塞是谁引起的掐掉这段以后Frame Timeline会变绿吗三个问题都能回答基本上就离根因不远了。上面这些案例都是从真实业务里扒出来的步骤和方法也经过了多次验证。希望这篇实战分析能帮你把perfetto用起来顺手别再被“卡顿”两个字折磨到半夜。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑