1、卡顿的本质:从Android渲染机制说起,理解16ms原则与垂直同步

大家好,我是老张。今天咱们聊聊卡顿这件事。

做Android开发这么多年,我见过太多人一遇到卡顿就慌了神。上来就抓trace、看CPU、查内存,折腾半天也不知道问题出在哪。其实啊,搞懂卡顿的本质,比学会一百种工具都重要

我个人习惯,遇到任何性能问题,先问自己三个问题:

  • 卡顿到底是怎么产生的?
  • 系统是怎么把画面显示出来的?
  • 16ms这个数字从哪来的?

这三个问题搞明白了,后面所有优化手段你都能自己推导出来。

1.1 从一张图片说起:人眼与流畅的错觉

先问个问题:你觉得电影是连续的吗?

其实不是。电影是一帧一帧的静态图片,每秒放24张,你的大脑就被骗了,觉得画面在动。这就是视觉暂留效应

Android手机也一样。系统不断刷新屏幕,每刷新一次就换一张图。如果刷新得够快,你就觉得流畅。如果慢了,你就觉得卡。

那多快才算「够快」?

嗯,这里有个关键数字——60fps。也就是每秒60帧。换算一下,每帧的生成时间不能超过16.67毫秒。这就是著名的16ms原则

核心结论:一帧必须在16.67ms内完成「CPU处理 → GPU渲染 → 屏幕显示」的全流程。超时了,你就卡了。

1.2 渲染流水线:CPU、GPU、屏幕在忙什么

你想想看,一个画面从代码到屏幕,中间经历了什么?

我画个简单的流程:

CPU处理布局和绘制 → 生成DisplayList → GPU栅格化 → 屏幕显示

每一步都有讲究:

  • CPU阶段:处理你的onMeasure、onLayout、onDraw。说白了就是算清楚每个View该放哪、长什么样。
  • GPU阶段:把CPU算好的数据变成像素点。这个叫栅格化。
  • 屏幕显示:把像素点刷到屏幕上。

我在项目中遇到过一个问题:某个列表滑动特别卡,抓trace一看,CPU阶段只用了2ms,但GPU阶段用了30ms。你猜怎么着?是某个自定义View里用了大量Path,GPU栅格化扛不住了。

我的经验:别总盯着CPU。有时候卡顿的根子在GPU。用Profile GPU Rendering工具看一眼,红色柱状图特别高,那就是GPU超时了。

1.3 16ms原则:为什么是16.67ms?

这个数字怎么来的?很简单:

1000ms ÷ 60fps ≈ 16.67ms

屏幕每秒刷新60次,每次刷新间隔就是16.67ms。你的应用必须在这个时间内完成一帧的所有工作。

但这里有个坑——不是所有手机都是60Hz。现在很多旗舰机是90Hz、120Hz甚至144Hz。算一下:

刷新率 每帧可用时间
60Hz 16.67ms
90Hz 11.11ms
120Hz 8.33ms
144Hz 6.94ms

看到了吗?高刷屏反而要求更苛刻。我曾经在120Hz的机型上排查卡顿,发现一个动画在60Hz手机上跑得挺流畅,到了120Hz就掉帧。原因就是动画计算量没变,但时间窗口缩了一半。

注意:别以为高刷屏就万事大吉。它只是给了你更流畅的体验上限,但同时也对性能提出了更高要求。如果你的应用在60Hz上刚好卡在16ms,那到120Hz上必卡无疑。

1.4 垂直同步:为什么画面会撕裂?

讲完16ms,咱们聊聊垂直同步。英文叫VSync。

先想象一个场景:屏幕正在从上往下刷新第1帧,但你的应用突然把第2帧的数据写进去了。结果就是——屏幕上半部分是第1帧,下半部分是第2帧。画面撕裂了。

为什么会这样?

因为CPU和GPU的渲染速度,跟屏幕的刷新速度是异步的。屏幕不管你有没有准备好,到时间就刷。你写一半我就读,那画面就乱了。

垂直同步的作用就是:让屏幕等一等。屏幕说:「我每次刷新前,先问一下GPU你准备好了没?准备好了我再刷。」

这样就不会撕裂了。但代价是什么?

代价就是——如果GPU没准备好,屏幕就重复显示上一帧。这就是卡顿的直接表现:画面不动了,或者跳了一下。

一句话总结:VSync用「重复帧」换来了「不撕裂」。而卡顿,就是重复帧出现得太频繁了。

1.5 卡顿的两种类型:掉帧与跳帧

在实际项目中,我遇到过两种卡顿:

  • 掉帧:某几帧没来得及渲染,屏幕重复显示上一帧。感觉就是画面突然停了一下。
  • 跳帧:某帧渲染时间太长,导致后面好几帧被跳过。感觉就是画面突然跳了一段。

举个例子:

正常情况:帧1 → 帧2 → 帧3 → 帧4 → 帧5
掉帧情况:帧1 → 帧1(重复) → 帧3 → 帧4 → 帧5
跳帧情况:帧1 → 帧2 → 帧5(跳过了3和4)

我曾经在一个电商App里排查首页卡顿。用户反馈说「滑动时图片会闪一下」。我抓了trace一看,是图片加载库在解码大图时占用了太多时间,导致连续跳了3帧。嗯,后来改成预解码+缩略图,问题就解决了。

避坑指南:我曾经以为卡顿就是掉帧。后来发现跳帧更隐蔽,用户感知更明显。排查时一定要看「帧间隔时间」,而不是只看「丢了几帧」。

1.6 小结:卡顿的本质就一句话

说了这么多,其实就一句话:

卡顿 = 一帧的渲染时间超过了16.67ms(或对应刷新率的阈值)

原因可能出在CPU(布局太复杂、主线程耗时操作),也可能出在GPU(过度绘制、复杂特效),还可能出在屏幕(刷新率不匹配)。

但不管原因是什么,最终表现都一样——VSync信号来了,但GPU没准备好,屏幕只能重复上一帧

下一章,我会带大家用Systrace和Profile GPU Rendering工具,亲手抓一次卡顿现场。到时候你会发现,原来卡顿的根子,就在那几毫秒里。

咱们下章见。