Frame Rate in Mobile Apps — What It Is, FPS and How to Improve

Author: IT Sectr Published: 2026-03-31 Reading time: 10 min

Frame Rate is the number of frames a graphics system displays per second. In mobile applications, the frame rate directly determines the smoothness of animations, scrolling, and transitions between screens. According to Android Developers, 2025, the target Frame Rate is 60 fps for standard displays and 120 fps for devices with high refresh rates. Deviation from the target value leads to visual stuttering and a degraded user experience.

Key Takeaways

  • Frame Rate is the number of frames per second (fps) that determines UI smoothness.
  • The standard target Frame Rate is 60 fps, corresponding to 16.6 ms per frame.
  • Devices with 120 Hz displays require 120 fps (8.3 ms per frame).
  • Missed frames cause Jank — noticeable animation stuttering.
  • Profiling Frame Rate is the first step toward UI performance optimization.

What Is Frame Rate

Frame Rate is a metric measured in frames per second (fps) that indicates how many times per second an application updates the image on the screen. The human eye perceives motion as smooth starting at 24 fps (cinema), but interactive UI requires at least 60 fps for touches and animations to feel instantaneous. Each frame is a complete cycle: processing user input, computing Layout, rendering the View hierarchy, and outputting to the screen. If any of these stages exceeds the allocated time budget (16.6 ms at 60 fps), the frame is skipped, and the user sees a stutter.

It is important to distinguish between the application's Frame Rate and the display's Refresh Rate. Refresh Rate is a screen characteristic: how many times per second the display physically updates the image (60, 90, 120, or 144 Hz). Frame Rate is how many frames per second the application manages to render. If the application outputs 60 fps on a 120 Hz display, every other frame will be duplicated — the image remains smooth but not as responsive as it could be. According to Google I/O 2023, modern flagships can maintain 120 fps in simple UI scenarios, but under heavy load (games, complex lists) the rate drops to 40–60 fps.

How Frame Rendering Works

Frame rendering in a mobile application goes through a pipeline of several stages. In Android, the pipeline includes: Input processing, Animation, Layout measurement and arrangement, Draw, GPU synchronization, and Screen output (Swap). Each stage runs on the CPU or GPU, and the total time of all stages must not exceed the frame budget. For 60 fps the budget is 16.6 ms, for 120 fps — 8.3 ms. Choreographer (Android) and CADisplayLink (iOS) synchronize rendering with the display's vertical blanking interval (VSync), ensuring the frame is output only at the moment of screen refresh, avoiding tearing.

In iOS, the pipeline is similar: Run Loop processes events, Core Animation computes layers, Render Server (a separate process) renders and sends the frame to the GPU. The difference in iOS is the dedicated Render Server process, which isolates rendering from the main application. If the application blocks the main thread, Render Server can still draw the last known frame, but animations will stop. If Render Server itself cannot keep up — the GPU idles and Frame Rate drops. According to Apple WWDC 2022, the most common causes of low Frame Rate in iOS are excessive CALayer nesting, heavy shadowPath, and offscreen rendering.

Tracking Frames via Choreographer

Kotlin code subscribes to Choreographer.FrameCallback and logs the actual time between frames. If the interval exceeds 16.6 ms, a missed frame is recorded.

kotlin
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)
    }
}

Display Refresh Rate and Frame Rate

Refresh Rate is a hardware characteristic of the display that determines how many times per second the screen physically redraws the image. Standard displays have 60 Hz, modern flagships have 90, 120, or 144 Hz. The application's Frame Rate can be lower than, equal to, or higher than the refresh rate (in the latter case, excess frames are discarded). The ideal scenario is when Frame Rate matches Refresh Rate: each hardware cycle receives a new frame from the application, and motion is maximally smooth. If Frame Rate is lower, the display repeats the last frame, which is perceived as micro-stuttering.

Android and iOS support dynamic refresh rate switching. Android 12+ uses Smart Refresh Rate: during scrolling the system raises the rate to 120 Hz, on static content it lowers to 60 Hz to save battery. iOS ProMotion (iPhone 13 Pro and newer) works similarly — the rate varies from 10 to 120 Hz depending on content. The developer should check whether the device supports high refresh rate and adapt the per-frame time budget. If the application cannot render a frame within 8.3 ms (for 120 Hz), it is better to force 60 Hz — this will ensure a stable Frame Rate without missed frames.

Display TypeRefresh RateFrame BudgetDevices
Standard60 Hz16.6 msMost Android/iOS
High90 Hz11.1 msOnePlus, Pixel 6+
Flagship120 Hz8.3 msiPhone Pro, Galaxy S22+
Gaming144 Hz6.9 msROG Phone, Nubia RedMagic

Frame Rate Measurement Tools

Both built-in platform tools and third-party profilers are available for measuring Frame Rate in mobile applications. In Android, the primary tool is GPU Profiling (Developer Options → Profile GPU Rendering), which shows a timeline of each frame broken down by stages (Draw, Prepare, Process, Execute). More detailed analysis is provided by Android Studio Profiler — it records a full rendering profile indicating specific Views causing redraws. In iOS, Instruments with the Core Animation template is used — it shows FPS, layer rendering time, and the number of offscreen renders.

For production Frame Rate monitoring, Firebase Performance (Android) is used — it collects Frame Rate in the background and aggregates by device, OS version, and session. In iOS, MetricKit provides similar data via MXAnimatoryMetric. For games and Flutter applications, FrameTimingCallback (Flutter) and Unity Profiler are used. It is important to measure not the average Frame Rate but percentiles: P50, P90, and P99. An application may show an average of 55 fps but have a P99 of 30 fps — this means 1% of the time users see severe stuttering, which is enough for negative reviews.

Measuring Frame Rate in Flutter

The Dart example shows how to subscribe to FrameTimingCallback in Flutter and log the number of missed frames. The callback fires after each completed frame.

dart
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}");
    }
}

Optimizing Frame Rate

Optimizing Frame Rate starts with identifying bottlenecks in the rendering pipeline. At the Layout stage, the main issues are excessive View hierarchy nesting, using relative layouts (RelativeLayout with many rules), and frequent requestLayout calls. The solution is to use ConstraintLayout or a flat hierarchy, avoiding nesting beyond 5–6 levels. At the Draw stage — overdraw: when a pixel is drawn multiple times per frame. For example, a white Activity background under a semi-transparent fragment, under which there is yet another layer — each pixel is drawn three times. The Debug GPU Overdraw tool shows problem areas with color coding. It is recommended to keep overdraw at 2x or below.

In iOS, the main issues are heavy cornerRadius and masksToBounds — they cause offscreen rendering, where Core Animation creates a temporary buffer, draws into it, then copies the result to the screen. Offscreen rendering is easily spotted in Instruments Core Animation: if the Renderer line is red — there are problems. The solution is to use UIImageView with pre-cropped images instead of cornerRadius, avoid groupOpacity and shouldRasterize unless absolutely necessary. For both platforms, it is critical to minimize the number of invalidate() and setNeedsDisplay() calls — each such call triggers a full view redraw cycle.

Optimizing Hierarchy in Android

The code demonstrates replacing deep RelativeLayout nesting with a flat ConstraintLayout structure. Reducing the nesting level from 4 to 1 cuts Layout time by 30–50%.

kotlin
// Example: flat structure via 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
        // binding data without redrawing the entire container
    }
}

Adaptive Frequencies and Dynamic Frame Rate

Modern mobile applications increasingly use adaptive Frame Rate — a system that dynamically adjusts the target frequency to the current scenario. Fast scrolling requires 120 fps for smoothness, while a static screen needs only 60 fps or even 30 fps for video. In Android, adaptation is implemented via Choreographer.setFrameInterval (API 33+) and Window.setFrameRate. The developer can specify a preferred frequency: setPreferredRefreshRate in SurfaceView or setFrameRate in Window. iOS automatically manages the frequency via ProMotion, but the developer can explicitly set preferredFramesPerSecond for CADisplayLink.

Dynamic Frame Rate is especially important for games and applications with animations. According to Google, reducing Frame Rate from 120 to 60 Hz on a static screen saves up to 30–40% GPU energy. To achieve the best balance between smoothness and power consumption, it is recommended to: measure the actual Frame Rate in different scenarios, set the target fps depending on the scene (game — 60, menu — 30, video — 24), and switch modes via Lifecycle-aware components so that the application does not waste resources rendering 120 fps in the background when minimized.

Setting Preferred Frame Rate

The Swift code sets preferredFramesPerSecond for CADisplayLink in iOS. During scrolling the rate increases to 120 Hz, upon stopping it decreases to 60 Hz.

swift
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() {
        // animation update
    }
}

Frequently Asked Questions

What Frame Rate is considered good for a mobile application?

For mobile applications, the target Frame Rate is 60 fps (16.6 ms per frame). For devices with 120 Hz displays, 120 fps is desirable. Values below 30 fps noticeably degrade the user experience.

How is Frame Rate different from display refresh rate?

Frame Rate is how many frames per second the application renders. Refresh Rate is how many times per second the display physically updates the image. When Frame Rate is below Refresh Rate, the display duplicates the last frame.

How to measure Frame Rate in Android?

Use GPU Profiling in Developer Options, Android Studio Profiler, or Firebase Performance. For programmatic measurement — Choreographer.FrameCallback with frame interval calculation.

What is overdraw and how does it affect Frame Rate?

Overdraw is drawing a single pixel multiple times per frame. Each extra layer increases the Draw phase time and reduces Frame Rate. Optimal overdraw is 2x, critical is 4x and above.

How does Dynamic Frame Rate save battery?

On static content, Dynamic Frame Rate reduces the frequency to 30–60 Hz, decreasing GPU load by 30–40%. During scrolling, the rate increases to 90–120 Hz for smoothness.

Summary

  • Frame Rate is the number of frames per second that determines UI and animation smoothness.
  • Target Frame Rate is 60 fps (16.6 ms per frame) for standard displays, 120 fps (8.3 ms) for High Refresh Rate.
  • Missed frames cause Jank — visible stuttering that degrades user experience.
  • Main causes of low Frame Rate are excessive View nesting, overdraw, and offscreen rendering.
  • Choreographer (Android) and CADisplayLink (iOS) synchronize rendering with VSync.
  • Adaptive Frame Rate balances smoothness and power consumption, reducing GPU load by up to 40%.
  • Profiling Frame Rate is the first step toward optimizing mobile application performance.

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also