移动应用中的FPS:本质、计算与优化

作者: IT Sectr 发布日期: 2026-04-01 阅读时间: 11 分钟

FPS(每秒帧数,Frames Per Second)——是一项衡量图形系统每秒渲染多少个独立帧的指标。在移动开发中,FPS是UI性能的标准指标:FPS越高,动画越流畅,界面响应越迅速。根据Google Android Performance, 2025的数据,移动应用的FPS目标值为每秒60帧——这是人眼将运动感知为连续和平滑的阈值。

要点

  • FPS——每秒帧数,UI流畅度的主要指标。
  • 目标值——60 FPS,每帧时间——16.6毫秒。
  • 对于高刷新率显示器,需要120 FPS(每帧8.3毫秒)。
  • FPS降至30以下肉眼可见卡顿和延迟。
  • 在生产环境中监控FPS有助于发现性能回归。

什么是FPS

FPS(每秒帧数,Frames Per Second)——是用于计算机图形、视频和移动界面的帧率测量单位。每一帧是在屏幕上短暂显示的静态图像。随着帧的快速切换,大脑将其感知为连续运动——这种效果称为视觉暂留。对于移动应用,FPS是一项关键指标,因为任何丢帧都会将流畅的动画变成明显的卡顿。应用必须严格在时间预算内完成每帧的渲染:对于60 FPS为16.6毫秒,对于90 FPS为11.1毫秒,对于120 FPS为8.3毫秒。

FPS不仅为UI测量,也用于游戏、视频和相机。在游戏中,FPS取决于场景复杂度、纹理质量和GPU性能。在视频中,FPS是固定的(24、30、60帧/秒),由内容决定。在移动应用中,FPS取决于UI代码的效率:布局的复杂度、视图数量、重绘频率以及GC(垃圾回收)的工作情况。根据Apple WWDC 2022的数据,由于集合更新效率低下(使用reloadData而非insert/delete/dequeueReusableCell),应用的平均FPS可能下降10–15%。实时测量FPS是QA工程师和性能开发人员的标准实践。

FPS如何计算

移动应用中的FPS计算基于连续帧之间的时间测量。最简单的公式:FPS = 1000 / deltaTimeMs,其中deltaTimeMs是前一帧完成与当前帧完成之间的间隔。如果当前帧在20毫秒内渲染完成,则FPS = 1000 / 20 = 50。然而在实践中,FPS即使在单秒内也鲜有稳定:典型的性能档案包括12–16毫秒的帧,中间夹杂着丢帧(jank)或慢帧(40–60毫秒)。因此,FPS被测量为1–5秒的移动平均值或帧时间分布的百分位数。

在Android中,FPS通过Choreographer计算,它从VSync(显示同步脉冲)接收回调。每个回调对应一帧。如果回调未到达——帧已丢失。Choreographer可以精确测量每秒帧数和丢帧数。在iOS中,CADisplayLink功能类似——每次显示器准备好渲染新帧时被调用。timestamp属性包含上一帧的精确时间,targetTimestamp属性包含下一帧的预期时间。两者之差即为当前帧的时间预算。

通过CADisplayLink监控FPS

Swift代码演示了通过CADisplayLink进行简单的FPS监控。frameCount计数器在每次调用时递增,每秒计算一次实际FPS。

swift
class FpsCounter {

    private var displayLink: CADisplayLink?
    private var frameCount = 0
    private var lastTime = TimeInterval(0)

    func start() {
        displayLink = CADisplayLink(
            target: self,
            selector: #selector(countFrame)
        )
        displayLink?.add(to: .current,
            forMode: .common)
    }

    @objc
    private func countFrame() {
        frameCount += 1
        let now = Date().timeIntervalSince1970

        if now - lastTime >= 1.0 {
            print("FPS: \(frameCount)")
            frameCount = 0
            lastTime = now
        }
    }
}

为什么60 FPS是标准

60 FPS(60 Hz)标准在行业中确立有几个原因。第一——生理原因:人眼在高于50–60 Hz的频率下无法区分单独的帧,将其感知为平滑运动。这个阈值称为临界闪烁融合频率(CFF,Critical Flicker Fusion)。第二——历史原因:早期的阴极射线管(CRT)在美国(NTSC)以60 Hz工作,在欧洲(PAL)以50 Hz工作。现代LCD显示器继承了这一频率。第三——工程原因:对于UI动画,60 FPS提供亚毫秒级的触摸响应延迟,这对于文本输入、滚动和拖拽至关重要。

对于移动开发者而言,60 FPS不仅是一个建议,更是每帧16.6毫秒的严格预算。此预算分配在渲染的所有阶段之间:Input(1–2毫秒)、Animation(2–3毫秒)、Layout(3–5毫秒)、Draw(3–5毫秒)和Swap(1–2毫秒)。如果任何阶段超出其子预算,帧可能无法在16.6毫秒内完成。Google Android Performance建议在帧准备上花费12–14毫秒,留出2–4毫秒的余量用于系统中断(GC、后台线程)。根据Firebase Performance的数据,平均FPS低于52且P99 FPS低于30的应用在Google Play评论中收到的性能投诉多35%。

FPS与帧时间的关系

FPS帧时间(Frame Time)——是同一指标的两个方面,重要的是不要混淆它们。FPS是速度,帧时间是延迟。在60 FPS下,每帧耗时16.6毫秒。在30 FPS下——33.3毫秒。但是FPS是一个非线性指标:从60降至30 FPS意味着帧时间翻了一番,而从30降至20 FPS则增加了1.5倍。因此性能分析工具显示的不是FPS,而是帧时间——这可以看到问题帧,而不是平均频率。例如,平均55 FPS可能掩盖5%的帧具有50–100毫秒的帧时间——这些帧导致卡顿(Jank),但对平均FPS影响不大。

在性能分析中,建议不要看平均FPS,而是看帧时间的直方图。在Android Studio Profiler和iOS Instruments中,帧时间以标尺形式显示,绿色区域表示不超过16.6毫秒(60 FPS),黄色区域表示16.6–33.3毫秒(30–60 FPS),红色区域表示超过33.3毫秒(低于30 FPS)。每个红色柱代表用户可察觉的延迟。实用规则:P95帧时间(95%的帧在X毫秒内完成)是比平均FPS更可靠的指标。如果P95帧时间超过32毫秒(30 FPS),即使平均FPS为50,应用也会被认为是缓慢的。

将帧时间转换为FPS

Kotlin函数用于将帧时间数组转换为包含百分位数的FPS。不仅返回平均FPS,还返回用于详细分析的P50、P90和P99。

kotlin
data class FpsReport(
    val average: Float,
    val p50: Float,
    val p90: Float,
    val p99: Float
)

fun List<Long>.toFpsReport(): FpsReport {
    val fpsValues = this.map { ms ->
        if (ms > 0) 1000f / ms else 0f
    }.sorted()

    return FpsReport(
        average = fpsValues.average().toFloat(),
        p50 = fpsValues[fpsValues.size / 2],
        p90 = fpsValues[(fpsValues.size * 90 / 100)],
        p99 = fpsValues[(fpsValues.size * 99 / 100)]
    )
}

高FPS与新显示器

配备90、120和144 Hz显示器的现代移动设备对FPS提出了新的要求。如果应用在120 Hz显示器上输出60 FPS,用户会看到微卡顿,因为每第二个屏幕刷新周期接收的是同一帧。为了维持120 FPS,每帧预算从16.6毫秒减少到8.3毫秒——这需要两倍高效的渲染代码。根据Android开发者的经验(Google I/O 2023),要实现稳定的120 FPS,需要:避免在Draw循环中进行内存分配,将视图层级中的视图数量降至最低(少于80个),放弃沉重的drawable转而使用VectorDrawable,并使用surfaceView处理复杂图形。

在iOS中情况类似:配备ProMotion(120 Hz)的iPhone Pro需要两倍的帧数,但每帧时间减半。Apple指出并非所有动画都必须在120 FPS下运行——Core Animation会自动为静态或缓慢变化的元素降低频率。然而,滚动、手势动画和过渡应该提供120 FPS以获得"丝滑"体验。从60 FPS过渡到120 FPS的主要问题包括:能耗增加(GPU增加25–40%)、设备发热和降频——因过热导致频率下降。建议实现回退机制:如果帧时间稳定超过8.3毫秒,则程序化地将目标频率降低到60 FPS,而不是等待系统降频。

60与120 FPS之间的切换器

Android的Java代码确定设备是否支持120 FPS,并切换渲染模式。使用Display.getMode来确定支持的频率。

java
class FpsModeSwitcher {

    static boolean canDo120Fps(Activity activity) {
        Display display = activity.getWindowManager()
            .getDefaultDisplay();
        for (Display.Mode mode : display.getSupportedModes()) {
            if (mode.getRefreshRate() >= 120f) {
                return true;
            }
        }
        return false;
    }
}

应用中的FPS优化

FPS优化需要系统的方法,从性能分析开始,以重构问题区域结束。第一阶段——使用性能分析工具测量当前FPS(。第二阶段——找到超出预算的帧。对于Android,可以通过GPU ProfilingPerfetto完成。对于iOS——使用Core Animation模板的Instruments。第三阶段——消除原因:减少过度绘制(overdraw),降低视图嵌套深度,将Layout阶段替换为ConstraintLayout,添加ViewHolder Recycling,将繁重计算移至后台线程。

特定于FPS的优化包括:帧率控制(Frame Pacing)——一种在帧之间均匀分配时间以避免快慢帧"堆积"的机制。在Android中,Choreographer.FrameCallback配合固定间隔可以实现帧率控制。在iOS中,CADisplayLink.preferredFrameRateRange执行同样的功能。第二种机制——三重缓冲(Triple Buffering):系统使用三个缓冲区而不是两个,允许GPU无需等待前一个缓冲区释放即可开始绘制下一帧。Android在需要时自动启用三重缓冲,但在iOS中,开发者可以通过CAMetalLayer明确请求。第三种——纹理缓存(Texture Caching):将位图缓存在GPU内存中,以避免每帧重新加载。

通过Choreographer实现帧率控制

Kotlin示例演示了以16.6毫秒固定间隔实现帧率控制(Frame Pacing)。即使系统延迟,所有回调也以均匀间隔到达。

kotlin
class PacedFrameRenderer {

    private val targetDelta = 16_666_666L // 16.6 ms (60 FPS)
    private var lastFrameTime = 0L

    private val frameCallback =
        Choreographer.FrameCallback { frameTimeNanos ->
            val delta = frameTimeNanos - lastFrameTime
            if (delta >= targetDelta) {
                onFrame(delta)
                lastFrameTime = frameTimeNanos
            }
            Choreographer.getInstance()
                .postFrameCallback(this)
        }

    private fun onFrame(delta: Long) {
        // 渲染帧
    }
}

常见问题

用户认为多少FPS是舒适的?

60 FPS——移动应用的舒适水平。60和120 FPS之间的差异仅在高刷新率显示器上的快速动画(滚动、拖拽)中可见。低于30 FPS——不舒适。

FPS如何与帧时间相关?

FPS = 1000 / 帧时间(毫秒)。如果帧时间 = 16.6毫秒,FPS = 60。如果帧时间 = 33.3毫秒,FPS = 30。建议监控帧时间而非FPS,因为它显示问题帧。

为什么滚动时FPS会下降?

滚动时,系统为列表的每个新元素调用LayoutDraw。如果视图复杂、布局未缓存或使用沉重的drawable——帧时间增加,FPS下降。解决方案——ViewHolder Recycling和扁平层级。

如何在iOS中测量FPS?

使用Core Animation模板的Instruments(实时显示FPS)。程序化测量——使用CADisplayLink计算每秒帧数。生产环境——使用MetricKit的MXAnimatoryMetric指标。

什么是三重缓冲以及它如何影响FPS?

三重缓冲(Triple Buffering)使用三个缓冲区而不是两个,允许GPU在当前帧的VSync完成之前开始渲染下一帧。这平滑了峰值负载并提高了FPS稳定性,但增加了1帧延迟。

总结

  • FPS——界面流畅度的关键指标,目标值为每秒60帧。
  • 帧时间(Frame Time)——比FPS更精确的指标,尤其是P95和P99百分位数。
  • 对于120 Hz显示器,需要120 FPS,每帧预算为8.3毫秒。
  • FPS下降的主要原因——过度绘制、视图深度嵌套、Draw循环中的内存分配。
  • 帧率控制三重缓冲有助于平滑帧时间的不均匀性。
  • FPS性能分析——通过GPU Profiling(Android)、Instruments Core Animation(iOS)、Firebase Performance进行。
  • 在生产环境中监控P95帧时间对于在用户大规模投诉之前发现回归至关重要。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读