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到来之前全部完成。晚一步,屏幕就只能重复上一帧内容。

一个小笔记: 60fps不等于流畅。帧率稳定的60fps才是流畅。如果帧率在60和20之间反复横跳,观感甚至比稳定30fps还差。这就是所谓的「帧率抖动」。

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阶段也可能卡住。这就是我常说的「主线程被撑死」。

避坑指南: 我曾经被一个Animator坑了一周。Animator的listener里写了复杂的字符串操作,每次触发都消耗5-8ms。看起来不多,但一旦动画循环起来,掉帧就接二连三出现。记住,不要在onAnimationUpdate里做耗时操作。

3.3 深度剖析:掉帧的「隐形杀手」—— VSync 机制

Android从4.1开始引入Choreographer,配合VSync(垂直同步)来规范帧的生产节奏。每个VSync信号到来,Choreographer唤醒开始处理三件事:

  1. 输入事件分发(CALLBACK_INPUT)
  2. 动画更新(CALLBACK_ANIMATION)
  3. 测量/布局/绘制(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帧的积压式掉帧。

核心结论: 掉帧的本质不是「CPU慢」或「GPU慢」这么简单。它是一个帧管道堵塞问题。任何一个环节(Input、Anim、Measure、Layout、Draw、Sync、Upload)卡住,都会导致整个管道延迟。

3.4 用户体验的「红线」:丢几帧用户能感觉到?

这个问题,我直接给结论:

  • 0帧丢 → 完美流畅,用户无感知
  • 丢1~2帧 → 视觉上几乎无感(但高频扫视下能察觉到微小卡顿)
  • 丢3~5帧 → 肉眼可见的「小卡」。比如滑列表时突然顿一下
  • 丢>5帧 → 明显卡顿,用户会烦躁、反复划动、甚至关应用

我记得有一次给电商App做优化。用户反馈在首页Banner轮播时「画面粘滞」。一抓trace发现,每4秒就有一次连续8帧掉帧,原因是GIF解码线程占满了CPU。优化后降到0掉帧,用户反馈转化率直接提升了3%。

我个人的修复优先级: 先把「连续掉帧>5帧」的场景灭掉。这类场景对用户体验伤害最深。然后再逐步优化那些偶发的2~3帧小卡,这部分往往是后台GC、线程调度抖动导致的,定位成本最高,但修复后收益明显。

3.5 一张图看懂帧率监控体系

帧率与掉帧知识体系 FPS 定义 屏幕刷新率(60Hz/120Hz) 软件帧率(FPS) VSync 同步窗口 正常帧(≤16.67ms) 掉帧(>16.67ms) 掉帧原因分类 CPU 抢资源 GPU 渲染超时 Input 处理超时 Animator 阻塞 用户体验影响 丢1~2帧(无感) 丢3~5帧(小卡) 丢>5帧(明显卡顿) 连续丢帧(粘滞) 修复优先级:连续大掉帧 → 偶发小卡 → 帧率抖动

这张图把咱们这章讲的核心内容串起来了。从FPS定义出发,到正常帧和掉帧的判定标准,再到掉帧的四大原因归类,最后落到用户体验量化。你用它做自查工具很合适。哪一环出问题,就去查哪一环,不用乱猜。