3、帧率与掉帧概念:FPS定义、掉帧原因、用户体验影响
做性能优化,绕不开一个词:帧率。很多人觉得帧率就是数字高低,60就行,30也行。嗯,这话其实只说对了一半。咱们今天把帧率这层窗户纸彻底捅破。
3.1 FPS —— 不只是个数字
FPS,全称 Frames Per Second,每秒传输帧数。说白了,就是手机屏幕在一秒钟内刷新了多少张画面。但别把它和「刷新率」搞混。刷新率是屏幕硬件的能力,固定60Hz或120Hz;FPS是软件实际给屏幕喂了多少帧。
- 60fps → 每帧16.67ms(行业基准线)
- 30fps → 每帧33.33ms(感觉卡,但还能忍)
- 120fps → 每帧8.33ms(丝滑,但也费电)
我个人习惯把16.67ms这根红线刻在脑子里。你想想看,一台手机屏幕60Hz,也就是每16.67ms中断一次,通知Choreographer该干活了。你的渲染逻辑必须在下一个VSync到来之前全部完成。晚一步,屏幕就只能重复上一帧内容。
3.2 掉帧 —— 到底丢在哪了?
掉帧,也叫Jank。意思就是本该在16.67ms内完成的工作,没完成。上一帧还被钉在屏幕上,下一帧已经错过了VSync窗口。
我最初接手性能优化的时候,以为掉帧都是CPU慢。查了一段时间才发现,掉帧的原因五花八门。我把它分成三大类:
外部抢资源导致的掉帧
比如后台突然启动了一个大任务,CPU被拉去跑别的了。我在项目中遇到过某直播App,刷Feed卡成PPT。当时查了半天,发现是图片解码线程抢CPU时间片抢得太凶,主线程饿着了。
GPU渲染超时
Layout没坏,代码也没死循环。但GPU画不过来。比如View层级叠了五六层,再加个半透明叠加、圆角裁切。GPU每帧都要拼命算合成,你说它累不累?
Choreographer自身调度延迟
嗯,这里要特别注意。即使CPU和GPU都空闲,如果主线程的MessageQueue里塞了一堆高优先级任务,比如Animator回调、Input事件、TraversalBatched,Choreographer的CALLBACK_INPUT和CALLBACK_ANIMATION阶段也可能卡住。这就是我常说的「主线程被撑死」。
3.3 深度剖析:掉帧的「隐形杀手」—— VSync 机制
Android从4.1开始引入Choreographer,配合VSync(垂直同步)来规范帧的生产节奏。每个VSync信号到来,Choreographer唤醒开始处理三件事:
- 输入事件分发(CALLBACK_INPUT)
- 动画更新(CALLBACK_ANIMATION)
- 测量/布局/绘制(CALLBACK_TRAVERSAL)
你想想看,如果第一个阶段就超时,后面两个阶段压根没机会执行。这也是为什么有时候App明明没做动画,却突然卡一下,可能就是Input处理里塞了个大活儿。
| 阶段 | 任务 | 典型耗时 | 超时后果 |
|---|---|---|---|
| CALLBACK_INPUT | 分发触摸等输入事件 | 1~3ms | 输入延迟感,严重时丢帧 |
| CALLBACK_ANIMATION | 执行所有活跃Animator回调 | 2~10ms | 动画卡顿、跳帧 |
| CALLBACK_TRAVERSAL | View树测量、布局、绘制 | 5~16ms+ | 页面绘制超时,掉帧 |
为什么16.67ms的窗口这么容易被突破?因为Android的渲染流程采用的是双缓冲机制。CPU生成一帧,放入缓冲区A;GPU消费缓冲区A的同时,CPU可以往缓冲区B写下一帧。这种并行听起来很美好,但一旦有一帧超时,缓冲区耗尽,后续帧就只能排队等待。最坏情况下,你会观察到连续3~5帧的积压式掉帧。
3.4 用户体验的「红线」:丢几帧用户能感觉到?
这个问题,我直接给结论:
- 0帧丢 → 完美流畅,用户无感知
- 丢1~2帧 → 视觉上几乎无感(但高频扫视下能察觉到微小卡顿)
- 丢3~5帧 → 肉眼可见的「小卡」。比如滑列表时突然顿一下
- 丢>5帧 → 明显卡顿,用户会烦躁、反复划动、甚至关应用
我记得有一次给电商App做优化。用户反馈在首页Banner轮播时「画面粘滞」。一抓trace发现,每4秒就有一次连续8帧掉帧,原因是GIF解码线程占满了CPU。优化后降到0掉帧,用户反馈转化率直接提升了3%。
3.5 一张图看懂帧率监控体系
这张图把咱们这章讲的核心内容串起来了。从FPS定义出发,到正常帧和掉帧的判定标准,再到掉帧的四大原因归类,最后落到用户体验量化。你用它做自查工具很合适。哪一环出问题,就去查哪一环,不用乱猜。