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 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.
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 Type | Cause | Typical Duration | Detection Tool |
|---|---|---|---|
| Layout | requestLayout, relayout | 5–30 ms | Perfetto, Systrace |
| Draw | Overdraw, heavy drawables | 3–20 ms | GPU Profiling |
| Thread | Main thread blocking | 10–200 ms | Android Studio Profiler |
| GC | Garbage Collection | 5–15 ms | Memory Profiler |
| Rendering | GPU load | 10–50 ms | GPU Tracer, Xcode GPU |
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.
Kotlin code subscribes to Choreographer.FrameCallback and logs each missed frame with the delay duration. The callback is invoked on each VSync.
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)
}
}
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.
Swift code detects missed frames using CADisplayLink. If the difference between timestamp and targetTimestamp exceeds 16.6 ms, Jank is recorded.
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
}
}
}
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.
Kotlin code uses FrameMetricsAggregator to collect per-frame statistics over a session. After stopping the aggregator, the number of missed frames is output.
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}%")
}
}
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.
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.
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
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).
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).
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.
Via Instruments with Core Animation or Metal System Trace template. For production — MetricKit with MXAnimatoryMetric. Programmatically — CADisplayLink checking the difference between timestamp and targetTimestamp.
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
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.
Read also