6、卡顿优化(上):卡顿产生的原因、Choreographer机制、Systrace工具使用、TraceView分析

卡顿,说白了就是用户觉得「不跟手」。你滑动列表时一顿一顿的,点个按钮半天没反应。这背后其实就一个核心问题:帧率掉到了人眼能感知的阈值以下

我刚开始做性能优化那会儿,总觉得卡顿是玄学。后来踩的坑多了,才发现它背后有一套非常清晰的逻辑链。今天我们就从根上把它扒开看看。

一、卡顿是怎么产生的?

Android的UI渲染,依赖一个叫VSync(垂直同步)的信号。系统每16.6ms发出一次VSync信号(60Hz屏幕),通知CPU和GPU:「该准备下一帧了」。

如果CPU或GPU没能在16.6ms内完成工作,这一帧就丢了。用户看到的就是卡顿。

我遇到过最典型的场景:某个列表页,用户快速滑动时,RecyclerViewonBindViewHolder里做了大量图片解码。结果就是——掉帧掉到亲妈都不认识。

卡顿的本质: 一帧的渲染耗时 > 16.6ms(60Hz)或 > 11.1ms(90Hz)。

具体原因可以归为三类:

  • CPU负载过高:主线程在做耗时操作,比如解析JSON、读写文件、复杂布局计算。
  • GPU负载过高:过度绘制、复杂Shader、大图渲染。
  • 内存问题:频繁GC导致线程暂停,或者内存抖动。

你想想看,一个ListView里每个Item都套三层LinearLayout,再加上半透明叠加——GPU不炸才怪。

二、Choreographer机制:Android的「帧调度器」

Choreographer是Android 4.1引入的。它的职责很简单:接收VSync信号,然后调度三大任务——输入处理、动画、绘制。

它的工作流程是这样的:

  1. 系统底层产生VSync信号。
  2. Choreographer收到信号,回调doFrame()
  3. doFrame()里依次执行:doCallbacks(INPUT)doCallbacks(ANIMATION)doCallbacks(TRAVERSAL)
  4. 如果doFrame()执行时间超过16.6ms,下一帧的VSync信号到来时,这一帧还没处理完——掉帧。

我记得有一次排查线上卡顿,发现某个页面在低端机上频繁掉帧。用Choreographer.FrameCallback打日志一看,doFrame的间隔经常跳到40ms以上。最后定位到是某个第三方SDK在onDraw里做了大量Path计算。

小技巧: 可以用 Choreographer.getInstance().postFrameCallback() 来监控帧率。虽然不能直接解决卡顿,但能帮你快速定位问题范围。

三、Systrace:系统级的「X光机」

Systrace是我个人最常用的工具。它能看到整个系统层面的调度情况——CPU频率、线程状态、Binder调用、SurfaceFlinger合成……

使用方式很简单:

# 抓取5秒的trace,重点关注gfx和view
python systrace.py -t 5 -o mytrace.html gfx view sched freq

打开生成的HTML文件,你会看到一条时间轴。重点关注这几个东西:

  • SurfaceFlinger:如果这里出现大段空白,说明GPU在等CPU。
  • 主线程(UI Thread):如果出现长条形的performTraversals,说明布局或绘制耗时。
  • RenderThread:如果这里出现断层,说明GPU提交命令卡住了。

我曾经遇到一个诡异的问题:某页面滑动时偶尔卡一下,但CPU和GPU看起来都不高。用Systrace一抓,发现是Binder: transaction耗时异常。顺藤摸瓜,发现是某个ContentProvider查询太慢。

注意: Systrace抓取时,手机不要插着USB充电线。充电电流会影响CPU频率调度,导致数据失真。我吃过这个亏,排查了半天发现是充电导致的假象。

四、TraceView:方法级别的「显微镜」

如果说Systrace是看宏观调度,那TraceView就是看微观方法耗时。它能精确告诉你:哪个方法占了多少CPU时间

使用方式:

Debug.startMethodTracing("app_trace");
// ... 你的业务代码 ...
Debug.stopMethodTracing();

生成的.trace文件,用Android Studio的Profiler打开。重点关注两个指标:

指标 含义 关注点
Inclusive Time 方法自身 + 子方法的总耗时 看整体瓶颈
Exclusive Time 方法自身代码的耗时(不含子方法) 看具体哪行代码慢
Calls + Real Time 调用次数 + 实际耗时 看是否频繁调用

我习惯先按Exclusive Time排序。为什么?因为Inclusive Time高可能是子方法的问题,但Exclusive Time高,说明这个方法自己就有问题。

举个例子:有一次我发现BitmapFactory.decodeStream的Exclusive Time特别高。点进去一看,是在主线程解码一张4K壁纸。解决方案很简单——扔到子线程,或者先用BitmapFactory.Options.inSampleSize降采样。

避坑指南: TraceView本身有性能开销。在低端机上,开启方法追踪可能会导致原本不卡的地方变卡。我建议你在中高端机型上抓trace,然后结合低端机的Systrace一起分析。

五、一张图理清卡顿优化的知识体系

下面这张SVG图,把卡顿产生的原因、检测工具、优化方向串起来了。你可以把它当作一个思维导图来用。

卡顿优化 卡顿产生的原因 CPU过载 | GPU过载 | 内存抖动 | GC频繁 Choreographer 机制 VSync → doFrame() → INPUT → ANIMATION → TRAVERSAL 检测工具 Systrace 系统级调度分析 TraceView 方法级耗时分析 核心目标:保证每帧在16.6ms内完成

嗯,到这里卡顿优化的上半部分就差不多了。我们聊了卡顿的本质、Choreographer怎么调度帧、Systrace和TraceView怎么用。这些工具和方法,是我在实际项目中反复验证过的。下次遇到卡顿,别慌——先抓Systrace看宏观,再用TraceView定位具体方法,最后对症下药。


公众号:蓝海资料掘金营,微信deep3321