Jank in Mobile Applications — What It Is, Causes, and Solutions

Author: IT Sectr Published: 2026-04-01 Reading time: 10 min

Jank is a term that refers to noticeable stuttering or “hiccups” in interface animation caused by missed individual frames. In mobile apps, Jank occurs when the frame rendering time exceeds the budget allocated by the display refresh rate. According to Android Developers, 2025, Jank is the main cause of the subjective feeling of “slowness” — an app can be functionally perfect, but the user perceives it as slow due to unstable FPS.

Key Takeaways

  • Jank — missed frames that appear as animation stuttering.
  • The main cause is exceeding the frame time budget (16.6 ms for 60 FPS).
  • Jank occurs due to heavy Layout, long GC, main thread blocking, or overdraw.
  • Diagnostics use FrameTimeline (Android) and Instruments (iOS).
  • Eliminating Jank increases NPS and user retention by 15–25%.

What Is Jank

Jank is a term from computer graphics that refers to a visual defect where animation moves in jerks instead of smooth motion. In mobile development, Jank is measured as the number of skipped frames per unit time. If the system fails to prepare a frame by the VSync moment, the display repeats the previous frame — a pause of 16.6 ms occurs at 60 Hz. A single missed frame may go unnoticed, but a series of 3–5 consecutive missed frames creates a feeling of “stutter” lasting 50–80 ms, which the user clearly notices.

Jank is especially critical for animations that need to run at a constant speed: scrolling feeds, menu open animations, parallax effects, and screen transitions. According to a Google UX study (2024), an app with a Jank rate above 3% of scroll sessions receives 22% more one-star reviews than an app with a rate below 0.5%. The Android Vitals tool automatically tracks Jank and classifies it by severity: moderate, severe, and critical.

Main Causes of Jank

The causes of Jank fall into several categories. The first is Layout Jank: caused by frequent requestLayout() calls due to View size changes, LayoutTransition animations, or dynamic content loading. Each requestLayout call triggers Measure + Layout for the entire View subtree, which can take 5–30 ms. The second is Draw Jank: related to overdraw and the use of heavy drawables. The third is Thread Jank: main thread blocking due to synchronous operations — file loading, database operations on the main thread, Bitmap decoding.

The fourth category is GC Jank: Garbage Collection in ART/Dalvik or Swift ARC. When many objects accumulate in the heap, GC triggers a Stop-The-World pause lasting 5–15 ms. On Android, GC pauses most often occur during frequent allocations in loops: creating objects in onDraw(), allocation in adapters, unused lambda expressions. The fifth is IPC Jank: inter-process communication (ContentProvider, Binder) on the main thread. The sixth is Rendering Jank: slow GPU rendering due to non-optimal shaders or large textures.

Jank TypeCauseTypical DurationDetection Tool
LayoutrequestLayout, relayout5–30 msPerfetto, Systrace
DrawOverdraw, heavy drawables3–20 msGPU Profiling
ThreadMain thread blocking10–200 msAndroid Studio Profiler
GCGarbage Collection5–15 msMemory Profiler
RenderingGPU load10–50 msGPU Tracer, Xcode GPU

Jank Diagnostics in Android

In Android, Jank diagnostics begin with the Perfetto system trace. Perfetto records the activity of all threads, CPU, GPU, and scheduler. A clear indicator of Jank is the Choreographer.doFrame and Choreographer.doCallbacks lines: if the interval between two consecutive doFrame calls exceeds 16.6 ms, a frame was missed. Perfetto shows the exact cause — which system call, lock, or GC caused the delay. In Android Studio Profiler, similar functionality is available through the CPU Profiler.

For automatic Jank detection in production, FrameMetricsAggregator is used — an API that collects statistics for each frame and aggregates them per session. In Android 12+, PerformanceHintManager was introduced — an API for hinting the system about the target frame rate. If the app indicates it is running in a 120 FPS scenario, the system can increase CPU/GPU frequency to prevent Jank. For simple logging of all missed frames, subscribing to Choreographer.FrameCallback is sufficient.

Logging Jank via Choreographer

Kotlin code subscribes to Choreographer.FrameCallback and logs each missed frame with the delay duration. The callback is invoked on each VSync.

kotlin
class JankDetector {

    private val frameBudget = 16_666_666L
    private var previousFrameTime = 0L

    private val callback =
        Choreographer.FrameCallback { currentTime ->
            if (previousFrameTime != 0L) {
                val frameDuration =
                    currentTime - previousFrameTime
                val skippedFrames =
                    (frameDuration / frameBudget) - 1
                if (skippedFrames > 0) {
                    Log.w("Jank",
                        "Skipped $skippedFrames frames")
                }
            }
            previousFrameTime = currentTime
            Choreographer.getInstance()
                .postFrameCallback(this)
        }

    fun start() {
        Choreographer.getInstance()
            .postFrameCallback(callback)
    }
}

Jank Diagnostics in iOS

In iOS, Jank diagnostics are performed using Instruments with the Core Animation template. Instruments shows real-time FPS, the number of offscreen renders, and hit tests. The main Jank indicators in iOS: red bars on the Core Animation timeline (frame budget exceeded), high Renderer metric (indicating offscreen rendering), and low FPS. For production monitoring, MetricKit collects reports with the MXAnimatoryMetric metric, which includes average FPS, P50, and P95 frame time.

Native Jank diagnostics in iOS include CADisplayLink with timestamp and targetTimestamp checking. If the current timestamp significantly lags behind targetTimestamp, one or more frames were missed. Apple also recommends using os_signpost for custom profiling: place a signpost-interval at the start and end of frame rendering and check in Instruments which intervals exceed 16.6 ms. In SwiftUI, UIView.invalidateIntrinsicContentSize is used for Jank diagnostics — frequent calls to this method indicate unstable Layout.

CADisplayLink for Jank Detection

Swift code detects missed frames using CADisplayLink. If the difference between timestamp and targetTimestamp exceeds 16.6 ms, Jank is recorded.

swift
class JankMonitor {

    private var displayLink: CADisplayLink?
    private var totalJank = 0

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

    @objc
    private func detectJank() {
        guard let link = displayLink else { return }
        let delay = link.targetTimestamp
            - link.timestamp
        if delay > 0.0167 {
            totalJank += 1
        }
    }
}

Jank Profiling Tools

For Jank profiling, both built-in OS tools and third-party SDKs are used. On Android, the key tool is Perfetto (which replaced Systrace). Perfetto can record traces up to 30 seconds long and analyze them via the web interface ui.perfetto.dev. It shows a precise timeline with Choreographer activity, rendering threads (RenderThread), and GPU. For detailed GPU problem analysis, AGI (Android GPU Inspector) is used, which shows not only frame time but also the load on specific GPU blocks — shaders, rasterizer, texture unit.

On iOS, the equivalent is Instruments with Core Animation, Metal System Trace, and GPU Driver templates. Core Animation shows FPS and frame time, Metal System Trace shows GPU work down to each draw call. For profiling on real devices under load, Firebase Performance (collects Screen Rendering metric) and Sentry (captures stack trace on Jank) are used. The new Android 15 Performance Hint API allows developers to tell the system which frames are important and receive warnings when Jank is approaching.

FrameMetricsAggregator in Production

Kotlin code uses FrameMetricsAggregator to collect per-frame statistics over a session. After stopping the aggregator, the number of missed frames is output.

kotlin
class JankAggregator(private val activity: Activity) {

    private val aggregator = FrameMetricsAggregator()

    fun startCollection() {
        aggregator.add(activity.window)
    }

    fun stopAndReport() {
        aggregator.remove()
        val result = aggregator.getMetrics()
        val totalFrames = result
            ?.get(FrameMetrics.TOTAL_DURATION)
            ?.size ?: 0
        val jankFrames = result
            ?.get(FrameMetrics.TOTAL_DURATION)
            ?.count { it > 16_666_666L} ?: 0
        Log.d("JankReport",
            "Jank ratio: \${jankFrames * 100 / totalFrames}%")
    }
}

Methods for Eliminating Frame Stuttering

Eliminating Jank requires a combination of techniques depending on its type. For Layout Jank: replace deep hierarchies with ConstraintLayout/Compose/SwiftUI, use merge tags, avoid requestLayout in animations. For Draw Jank: use Debug GPU Overdraw to find 4x+ overdraw, replace heavy drawables with vector graphics (VectorDrawable/PDF), use hardware layers with caution — they speed up rendering but consume more GPU memory. For Thread Jank: move all I/O operations, database work, and Bitmap decoding to background threads, use Kotlin Coroutines with the correct Dispatcher or RxJava with Schedulers.io().

For GC Jank: minimize allocations in onDraw() and getView(), use object pools (ObjectPool), replace for-each with indexed for, use immutable data classes in Kotlin with copy() carefully — copy creates a new object. For IPC Jank: initialize ContentProvider lazily via App Startup, move Binder calls to a background thread. For Rendering Jank: reduce texture sizes to the maximum screen resolution, use ASTC or ETC2 compression, avoid excessive shader compilation (compile shaders in advance). A comprehensive solution is regular Perfetto/Instruments profiling in CI and tracking Jank regressions.

Anti-Jank Pattern: Async Layout

Kotlin code demonstrates asynchronous data loading onto the screen after reportFullyDrawn, so heavy work does not block the first frame. The callback is invoked after the user sees the interface.

kotlin
class JankSafeLoader {

    suspend fun loadAfterFirstFrame(
        activity: Activity
    ) {
        // guarantee that the first frame is already rendered
        if (Build.VERSION.SDK_INT >= 29) {
            activity.reportFullyDrawn()
        }

        // heavy loading — after the first frame
        withContext(Dispatchers.IO) {
            val data = fetchHeavyData()
            withContext(Dispatchers.Main) {
                updateUI(data)
            }
        }
    }
}

Frequently Asked Questions

What is Jank in mobile apps?

Jank is missed rendering frames that appear as noticeable stuttering or jerks in animation. It occurs when frame preparation time exceeds the time budget (16.6 ms for 60 FPS).

What are the main causes of Jank?

Layout Jank (frequent requestLayout), Draw Jank (overdraw), Thread Jank (main thread blocking), GC Jank (garbage collection), IPC Jank (Binder calls), and Rendering Jank (heavy shaders).

How to diagnose Jank in Android?

Use Perfetto for system tracing, GPU Profiling for frame phase analysis, and FrameMetricsAggregator for production monitoring. In Android Studio — CPU Profiler with Deep Java Trace.

How to measure Jank in iOS?

Via Instruments with Core Animation or Metal System Trace template. For production — MetricKit with MXAnimatoryMetric. Programmatically — CADisplayLink checking the difference between timestamp and targetTimestamp.

What Jank percentage is considered critical?

According to Google, a Jank rate above 3% of scroll sessions (3 out of 100 scrolls contain stutter) leads to a 22% increase in negative reviews. The target rate is less than 0.5% of scroll sessions.

Summary

  • Jank — missed frames that cause visible animation stuttering in mobile apps.
  • Main causes: Layout Jank, Draw Jank, Thread Jank, GC Jank, IPC Jank, and Rendering Jank.
  • Jank diagnostics on Android — via Perfetto, GPU Profiling, and FrameMetricsAggregator.
  • Jank diagnostics on iOS — via Instruments, CADisplayLink, and MetricKit.
  • Eliminating Jank requires a combination of: flat hierarchies, background threads, minimized allocations, caching.
  • Target Jank rate — less than 0.5% of scroll sessions with stuttering.
  • Regular CI profiling prevents performance regressions before they reach production.

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