Frame Rate — 是图形系统在一秒内显示的帧数。在移动应用中,帧频直接决定动画、滚动和屏幕切换的流畅度。根据 Android Developers, 2025 的数据,目标 Frame Rate 对于标准显示屏为60 fps,对于高刷新率设备为120 fps。偏离目标值会导致视觉卡顿和用户体验下降。
要点
Frame Rate(帧率)— 是一种以每秒帧数(fps)为单位的度量,表示应用每秒更新屏幕图像的次数。人眼从24 fps(电影)开始将运动感知为流畅,但对于交互式UI,至少需要60 fps才能使触摸和动画感觉即时。每一帧都是一个完整的周期:处理用户输入、计算布局、渲染视图层次和输出到屏幕。如果任何阶段超过了分配的时间预算(60 fps时为16.6毫秒),帧将被丢弃,用户会看到卡顿。
区分应用的 Frame Rate 与显示屏的刷新率(Refresh Rate)很重要。刷新率是屏幕的特性:显示屏每秒物理刷新图像的次数(60、90、120或144 Hz)。Frame Rate — 是应用每秒能够渲染的帧数。如果应用在120 Hz显示屏上输出60 fps,每第二帧将被重复 — 图像保持流畅,但不如应有的响应灵敏。根据Google I/O 2023的数据,现代旗舰机可以在简单的UI场景中保持120 fps,但在重负载下(游戏、复杂列表)频率下降到40–60 fps。
移动应用中的帧渲染经过多个阶段的管道。在Android中,管道包括:输入处理(Input)、动画(Animation)、测量和布局(Layout)、绘制(Draw)、与GPU同步和输出到屏幕(Swap)。每个阶段在CPU或GPU上执行,所有阶段的总时间不得超过帧预算。对于60 fps,预算为16.6毫秒,对于120 fps — 8.3毫秒。Choreographer(Android)和 CADisplayLink(iOS)将渲染与显示屏的垂直刷新(VSync)同步,确保帧仅在屏幕刷新时显示,避免图像撕裂(tearing)。
在iOS中,管道类似:Run Loop 处理事件,Core Animation计算图层,Render Server(独立进程)渲染并将帧发送到GPU。iOS的区别在于独立的Render Server进程,它将渲染与主应用隔离。如果应用阻塞主线程,Render Server仍然可以显示最后一个已知帧,但动画将停止。如果Render Server本身跟不上 — GPU空闲,Frame Rate下降。根据Apple WWDC 2022的数据,iOS中Frame Rate低的最常见原因是CALayer过度嵌套、繁重的shadowPath和离屏渲染(offscreen rendering)。
Kotlin代码订阅 Choreographer.FrameCallback 并记录帧之间的实际时间。如果间隔超过16.6毫秒 — 记录丢帧。
class FrameRateMonitor {
private var lastFrameTime = 0L
private val frameCallback =
Choreographer.FrameCallback { frameTimeNanos ->
if (lastFrameTime != 0L) {
val deltaMs = (frameTimeNanos - lastFrameTime) / 1_000_000f
if (deltaMs > 16.6f) {
Log.w("FrameRate",
"Skipped frame: $deltaMs ms")
}
}
lastFrameTime = frameTimeNanos
Choreographer.getInstance()
.postFrameCallback(this)
}
fun start() {
Choreographer.getInstance()
.postFrameCallback(frameCallback)
}
}
Refresh Rate(刷新率)— 是显示屏的硬件特性,决定屏幕每秒物理重绘图像的次数。标准显示屏为60 Hz,现代旗舰机 — 90、120或144 Hz。应用的 Frame Rate 可以低于、等于或高于刷新率(在后一种情况下,多余的帧被丢弃)。理想情况 — Frame Rate与Refresh Rate一致:每个硬件周期从应用获得新帧,运动最流畅。如果Frame Rate较低,显示屏重复最后一帧,这会被感知为微卡顿(stutter)。
Android和iOS支持动态切换刷新率。Android 12+使用 Smart Refresh Rate:滚动时系统将频率提高到120 Hz,静态内容时降低到60 Hz以节省电池。iOS ProMotion(iPhone 13 Pro及更新机型)类似工作 — 频率根据内容在10到120 Hz之间变化。开发者应检查设备是否支持高频率,并调整每帧的时间预算。如果应用无法在8.3毫秒内(对于120 Hz)渲染帧,最好强制以60 Hz工作 — 这将确保稳定的Frame Rate而不会丢帧。
| 显示屏类型 | Refresh Rate | 每帧预算 | 设备 |
|---|---|---|---|
| 标准 | 60 Hz | 16.6毫秒 | 大多数Android/iOS |
| 高 | 90 Hz | 11.1毫秒 | OnePlus, Pixel 6+ |
| 旗舰 | 120 Hz | 8.3毫秒 | iPhone Pro, Galaxy S22+ |
| 游戏 | 144 Hz | 6.9毫秒 | ROG Phone, Nubia RedMagic |
在移动应用中测量 Frame Rate,既有平台内置工具,也有第三方分析器可用。在Android中,主要工具是 GPU Profiling(Developer Options → Profile GPU Rendering),它按阶段(Draw, Prepare, Process, Execute)显示每个帧的时间线。更详细的分析由 Android Studio Profiler 提供 — 它记录完整的渲染配置文件,指出导致重绘的特定View。在iOS中,使用 Instruments 与 Core Animation 模板 — 显示FPS、图层渲染时间和离屏渲染次数。
用于生产环境的Frame Rate监控使用 Firebase Performance(Android)— 它在后台收集 Frame Rate,并按设备、操作系统版本和会话进行聚合。在iOS中,MetricKit 通过 MXAnimatoryMetric 提供类似数据。对于游戏和Flutter应用,使用 FrameTimingCallback(Flutter)和 Unity Profiler。重要的是测量不是平均Frame Rate,而是百分位数:P50、P90和P99。应用可能显示平均55 fps,但P99 = 30 fps — 这意味着1%的时间用户看到严重卡顿,这足以导致负面评价。
Dart示例展示了如何在Flutter中订阅 FrameTimingCallback 并记录丢帧数。回调在每个完成的帧之后触发。
import 'package:flutter/scheduler.dart';
class FrameRateLogger {
int totalFrames = 0;
int missedFrames = 0;
void start() {
SchedulerBinding.instance
.addTimingsCallback(_onReportTimings);
}
void _onReportTimings(List<FrameTiming> timings) {
for (final timing in timings) {
totalFrames++;
if (timing.totalSpan()
> Duration(milliseconds: 16)) {
missedFrames++;
}
}
debugPrint("FPS: \${totalFrames - missedFrames}");
}
}
优化 Frame Rate 从识别渲染管道中的瓶颈开始。在布局阶段,主要问题是视图层次过度嵌套、使用相对布局(具有大量规则的RelativeLayout)和频繁调用requestLayout。解决方案 — 使用ConstraintLayout或扁平层次,避免超过5–6层嵌套。在绘制阶段 — 过度绘制(overdraw):当像素每帧被绘制多次时。例如,半透明片段下的Activity白色背景,其下还有一层 — 每个像素被绘制三次。Debug GPU Overdraw 工具通过颜色指示显示问题区域。建议将overdraw保持在2x或更低水平。
在iOS中,主要问题是繁重的 cornerRadius 和 masksToBounds — 它们导致离屏渲染(offscreen rendering),Core Animation创建临时缓冲区,在其中绘制,然后将结果复制到屏幕。离屏渲染在Instruments Core Animation中很容易发现:如果 Renderer 行为红色 — 存在问题。解决方案 — 使用带有预裁剪图像的 UIImageView 代替cornerRadius,避免在没有必要时使用 groupOpacity 和 shouldRasterize。对于两个平台,最小化 invalidate() 和 setNeedsDisplay() 的调用次数至关重要 — 每次这样的调用都会启动完整的视图重绘周期。
代码演示了将RelativeLayout的深层嵌套替换为 ConstraintLayout 的扁平结构。将嵌套级别从4减少到1可使布局时间缩短30–50%。
// 示例:通过ConstraintLayout的扁平结构
class OptimizedView(context: Context) :
ConstraintLayout(context) {
private val binding =
ItemProfileBinding.inflate(
LayoutInflater.from(context)
)
fun bind(user: User) {
binding.avatar.setImageURI(user.avatarUrl)
binding.nameText.text = user.name
// 绑定数据而无需重绘整个容器
}
}
现代移动应用越来越多地使用自适应 Frame Rate — 一种根据当前场景动态调整目标帧率的系统。快速滚动时列表需要120 fps保证流畅性,在静态屏幕上60 fps甚至30 fps用于视频就足够了。在Android中,自适应通过 Choreographer.setFrameInterval(API 33+)和 Window.setFrameRate 实现。开发者可以向系统指示首选帧率:在SurfaceView中 setPreferredRefreshRate 或在Window中 setFrameRate。iOS通过 ProMotion 自动管理帧率,但开发者可以显式设置CADisplayLink的 preferredFramesPerSecond。
动态帧率对于游戏和具有动画的应用尤其重要。根据Google数据,在静态屏幕上将Frame Rate从120 Hz降低到60 Hz可节省30–40%的GPU能耗。为了在流畅性和功耗之间实现最佳平衡,建议:在不同场景中测量实际Frame Rate,根据场景设置目标fps(游戏 — 60,菜单 — 30,视频 — 24),并通过生命周期感知组件切换模式,以便应用在最小化时不会在后台浪费资源渲染120 fps。
Swift代码为iOS中CADisplayLink设置 preferredFramesPerSecond。滚动时频率升至120 Hz,停止时 — 降至60 Hz。
class AdaptiveFrameRateManager {
private var displayLink: CADisplayLink?
func startWithHighRate() {
displayLink = CADisplayLink(
target: self,
selector: #selector(step)
)
if #available(iOS 15.0, *) {
displayLink?.preferredFrameRateRange =
CAFrameRateRange(
minimum: 60,
maximum: 120,
preferred: 120
)
}
displayLink?.add(to: .current,
forMode: .common)
}
@objc
private func step() {
// 动画更新
}
}
常见问题
对于移动应用,目标 Frame Rate 是60 fps(每帧16.6毫秒)。对于配备120 Hz显示屏的设备,建议120 fps。低于30 fps的值会明显降低用户体验。
Frame Rate — 应用每秒渲染的帧数。Refresh Rate — 显示屏每秒物理刷新图像的次数。当Frame Rate低于Refresh Rate时,显示屏重复最后一帧。
使用 GPU Profiling(开发者选项)、Android Studio Profiler 或 Firebase Performance。用于程序化测量 — 计算帧间隔的 Choreographer.FrameCallback。
Overdraw — 同一像素每帧被多次绘制。每个额外层增加绘制阶段的时间并降低Frame Rate。最佳overdraw为2x,临界 — 4x及以上。
在静态内容下,Dynamic Frame Rate 将频率降至30–60 Hz,GPU负载降低30–40%。滚动时频率升至90–120 Hz以保证流畅性。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。