Jank and dropped frames

Performance Mid-level Kotlin 2.0+ API 24+ Reviewed By Best Latest Apps

Order the steps

Order the steps to fix janky scrolling.

  • Fix the biggest cause, such as decoding on main
  • Reproduce on a mid-range device with a release-like build
  • Measure with Macrobenchmark FrameTimingMetric
  • Measure again and compare
  • Capture a Perfetto trace and find the long frames

Choose an answer, then open Show answer below to check it.

Show answer

Answer: B C E A D

  1. Reproduce on a mid-range device with a release-like build Debug builds lie about performance.
  2. Measure with Macrobenchmark FrameTimingMetric Get a number to improve.
  3. Capture a Perfetto trace and find the long frames See what each slow frame did.
  4. Fix the biggest cause, such as decoding on main One change at a time.
  5. Measure again and compare Prove the fix worked.
Six frames at 60 Hz: smooth scrolling, then scrolling while a bitmap is decoded on the main thread.

Illustrative, based on Compose and Android rules; not a device recording

Text version
Smooth
FrameWork (ms)Result
F19on time
F211on time
F310on time
F412on time
F59on time
F610on time

0 dropped frames at 60 Hz (16.7 ms budget); ANR dialog at 5 s: no.

Decode on main
FrameWork (ms)Result
F110on time
F245⏱ late (the next 2 dropped)
F3–✕ dropped (F2 still running)
F4–✕ dropped (F2 still running)
F511on time
F610on time

2 dropped frames at 60 Hz (16.7 ms budget); ANR dialog at 5 s: no.

30-second answer

Jank is when a frame isn't ready in time: 16.7 ms at 60 Hz, 8.3 ms at 120 Hz. Causes include heavy work on the main thread, expensive layout or recomposition, large bitmaps decoded on main, overdraw and GC pauses from allocation. Find it with JankStats or Macrobenchmark's FrameTimingMetric for numbers, and a Perfetto system trace to see what each slow frame was doing.

Practise saying it · 35 s. No recording, just a timer

Explain jank and how to find it.

Say your answer out loud, then check which of these points you covered:

  • Frame budget 16.7 / 8.3 ms
  • Main-thread work, layout, GC
  • Measure with Macrobenchmark
  • Trace with Perfetto
  • Release builds

Deep dive

The RenderThread draws on the GPU; a slow main thread still means late frames.

Profile release-like builds; debug builds are much slower, especially for Compose.

Use trace("name") { } sections to label your own code in traces.

High refresh rates halve the budget, so code that was fine at 60 Hz can jank at 120 Hz.

Common mistakes

  • Profiling debug builds.
  • Guessing at fixes without a trace.
  • Only testing on flagship phones.

Senior take & follow-ups

Name the frame budget at two refresh rates and a measure-first workflow.

Follow-up questions

Think of your answer first, then open the hint.

  1. Why profile release builds?

    Show hint

    Debug builds disable optimisations and Compose runs much slower.

  2. How do you label your code in a trace?

    Show hint

    androidx.tracing's trace { } blocks.

Compare on iOS: Dropped frames and hitches The same frame-budget problem; iOS uses Instruments instead of Perfetto.

Suggest a fix if something here is wrong or out of date.