6、卡顿优化(上):卡顿产生的原因、Choreographer机制、Systrace工具使用、TraceView分析
卡顿,说白了就是用户觉得「不跟手」。你滑动列表时一顿一顿的,点个按钮半天没反应。这背后其实就一个核心问题:帧率掉到了人眼能感知的阈值以下。
我刚开始做性能优化那会儿,总觉得卡顿是玄学。后来踩的坑多了,才发现它背后有一套非常清晰的逻辑链。今天我们就从根上把它扒开看看。
一、卡顿是怎么产生的?
Android的UI渲染,依赖一个叫VSync(垂直同步)的信号。系统每16.6ms发出一次VSync信号(60Hz屏幕),通知CPU和GPU:「该准备下一帧了」。
如果CPU或GPU没能在16.6ms内完成工作,这一帧就丢了。用户看到的就是卡顿。
我遇到过最典型的场景:某个列表页,用户快速滑动时,RecyclerView的onBindViewHolder里做了大量图片解码。结果就是——掉帧掉到亲妈都不认识。
具体原因可以归为三类:
- CPU负载过高:主线程在做耗时操作,比如解析JSON、读写文件、复杂布局计算。
- GPU负载过高:过度绘制、复杂Shader、大图渲染。
- 内存问题:频繁GC导致线程暂停,或者内存抖动。
你想想看,一个ListView里每个Item都套三层LinearLayout,再加上半透明叠加——GPU不炸才怪。
二、Choreographer机制:Android的「帧调度器」
Choreographer是Android 4.1引入的。它的职责很简单:接收VSync信号,然后调度三大任务——输入处理、动画、绘制。
它的工作流程是这样的:
- 系统底层产生VSync信号。
- Choreographer收到信号,回调
doFrame()。 doFrame()里依次执行:doCallbacks(INPUT)、doCallbacks(ANIMATION)、doCallbacks(TRAVERSAL)。- 如果
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查询太慢。
四、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降采样。
五、一张图理清卡顿优化的知识体系
下面这张SVG图,把卡顿产生的原因、检测工具、优化方向串起来了。你可以把它当作一个思维导图来用。
嗯,到这里卡顿优化的上半部分就差不多了。我们聊了卡顿的本质、Choreographer怎么调度帧、Systrace和TraceView怎么用。这些工具和方法,是我在实际项目中反复验证过的。下次遇到卡顿,别慌——先抓Systrace看宏观,再用TraceView定位具体方法,最后对症下药。
公众号:蓝海资料掘金营,微信deep3321