Measuring Performance
Profilers, traces and benchmarks.
Measuring Performance is a free Android Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Android Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Measure Before You Optimize
The golden rule of performance work: measure first, guess never. Human intuition about what is slow is almost always wrong.
In this lesson you will learn the tools Android gives you to see performance: the profilers, system traces, and microbenchmarks. Once you can measure, every optimization becomes a data-driven decision instead of a hunch.
- Profilers show CPU, memory and energy in real time.
- System traces reveal exactly where each frame's time goes.
- Benchmarks give repeatable numbers you can compare across builds.
The 16ms Frame Budget
On a 60Hz screen, the system draws a new frame every 16.67ms. If your app cannot prepare a frame in that window, the frame is dropped and users perceive jank (stutter). On 120Hz devices the budget shrinks to about 8ms.
Performance work is really about staying inside this budget. The comment below shows the math you keep in your head.
// Frame budget math
// 60 Hz -> 1000ms / 60 = 16.67ms per frame
// 90 Hz -> 1000ms / 90 = 11.11ms per frame
// 120 Hz -> 1000ms / 120 = 8.33ms per frame
//
// Exceed the budget on the UI thread = a dropped frame = visible jank.
fun frameBudgetMs(refreshHz: Int): Double = 1000.0 / refreshHz
fun main() {
println("60Hz -> %.2f ms".format(frameBudgetMs(60)))
println("120Hz -> %.2f ms".format(frameBudgetMs(120)))
}Android Studio Profiler
The Android Studio Profiler (View > Tool Windows > Profiler) attaches to a running app and shows live timelines for CPU, Memory, Energy and Network.
- CPU: record method traces and system traces to find hot code paths.
- Memory: watch allocations and capture heap dumps to find leaks.
- Energy: spot wakelocks and excessive jobs draining battery.
Always profile a release-style build on a real device. Debug builds and emulators give misleading numbers because optimizations are disabled.
Macrobenchmark for Real Scenarios
The Jetpack Macrobenchmark library measures whole user journeys (startup, scrolling, navigation) on a real device and reports stable metrics like frame timing and startup time.
You write a test that launches your app and exercises a flow; the library runs it many times and gives you median and 99th-percentile numbers.
@RunWith(AndroidJUnit4::class)
class StartupBenchmark {
@get:Rule val rule = MacrobenchmarkRule()
@Test
fun coldStartup() = rule.measureRepeated(
packageName = "com.example.app",
metrics = listOf(StartupTimingMetric()),
iterations = 5,
startupMode = StartupMode.COLD
) {
pressHome()
startActivityAndWait()
}
}Microbenchmark for Hot Code
When you need to know how fast a single function is, use the Microbenchmark library. It runs your code in a tight loop, warms up the JIT, and reports nanoseconds per operation with the noise stripped out.
Use it for parsers, serializers, sorting, or any CPU-bound helper you call frequently.
@RunWith(AndroidJUnit4::class)
class JsonParseBenchmark {
@get:Rule val benchmarkRule = BenchmarkRule()
@Test
fun parseLargePayload() {
val raw = loadSampleJson()
benchmarkRule.measureRepeated {
val parsed = parseUsers(raw) // code under test
// assertEquals avoids the compiler dropping the result
assertTrue(parsed.isNotEmpty())
}
}
}System Tracing with Perfetto
System tracing captures what every thread and the system did, frame by frame. Android Studio's CPU profiler can record one, and you can also capture with adb and open it in Perfetto (ui.perfetto.dev).
A trace shows the dreaded red frames, the work on the main thread, and time spent in the GPU. It is the single best tool for diagnosing jank.
# Record a 5-second system trace from the command line
adb shell perfetto -o /data/misc/perfetto-traces/trace.perfetto-trace \
-t 5s sched freq idle am wm gfx view
# Pull it to your machine, then open in https://ui.perfetto.dev
adb pull /data/misc/perfetto-traces/trace.perfetto-traceCustom Trace Sections
Out of the box, traces show framework work. To label your own code, wrap it in a named trace section. These names then appear as labelled blocks in Perfetto, making your hotspots easy to spot.
Use the androidx.tracing API so sections show up in both debug and benchmark traces.
import androidx.tracing.trace
fun loadDashboard(repo: Repo): Dashboard {
return trace("loadDashboard") {
val user = trace("fetchUser") { repo.user() }
val feed = trace("fetchFeed") { repo.feed() }
Dashboard(user, feed)
}
}
// In Perfetto you will now see named slices:
// loadDashboard > fetchUser, fetchFeedTracking Dropped Frames at Runtime
You do not always have a profiler attached. JankStats is a Jetpack library that reports janky frames from inside your running app, so you can log them or send aggregates to analytics.
It hooks into the window and calls you back whenever a frame exceeds its budget.
val jankStats = JankStats.createAndTrack(window) { frameData ->
if (frameData.isJank) {
Log.w("Jank", "Janky frame: ${frameData.frameDurationUiNanos} ns")
analytics.logJank(frameData.frameDurationUiNanos)
}
}
// Pause/resume with your screen lifecycle
override fun onResume() { super.onResume(); jankStats.isTrackingEnabled = true }
override fun onPause() { super.onPause(); jankStats.isTrackingEnabled = false }Reading the Numbers: Percentiles
A single average hides pain. If your average frame is 10ms but the 99th percentile is 40ms, 1% of frames are janky and users feel it. Always look at P50, P90, P99, not just the mean.
Benchmarks report these for you. The helper below shows the idea on raw frame samples.
fun percentile(samplesMs: List<Double>, p: Int): Double {
val sorted = samplesMs.sorted()
val index = ((p / 100.0) * (sorted.size - 1)).toInt()
return sorted[index]
}
fun main() {
val frames = listOf(8.0, 9.0, 10.0, 9.5, 11.0, 40.0, 9.0, 10.5)
println("P50 = %.1f ms".format(percentile(frames, 50)))
println("P99 = %.1f ms".format(percentile(frames, 99)))
}A Repeatable Measurement Workflow
Trustworthy numbers come from a disciplined process. Follow this checklist every time:
- Use a release / non-debuggable build (R8 enabled).
- Run on a physical device, plugged in, with a stable thermal state.
- Repeat the scenario several times and report the median.
- Change one thing at a time and re-measure.
- Compare against a saved baseline so you can prove improvement.
If you skip these, you will chase noise instead of fixing real problems.
Where Time Actually Goes
Most Android jank comes from a small set of causes. Knowing them tells you where to point your tools:
- Main-thread work: disk/network/JSON on the UI thread.
- Over-recomposition: Compose redrawing far too much.
- Allocation churn: garbage collection pauses.
- Layout passes: deeply nested or repeatedly measured layouts.
The next lessons in this course tackle recomposition, memory leaks, and startup directly. Measurement is what tells you which one to fix first.
Quick Check
You want a stable, repeatable measurement of how long your app's cold startup takes on a real device across multiple runs. Which tool fits best?
Recap: You Can Now Measure
You learned to make performance visible before changing anything:
- The 16ms frame budget defines smooth vs. janky.
- The Android Studio Profiler shows live CPU, memory and energy.
- Macro- and Microbenchmarks give repeatable numbers.
- Perfetto traces and custom
trace()sections reveal where time goes. - JankStats reports jank from inside the running app.
- Read P50/P90/P99, profile release builds on real devices, and change one thing at a time.
Next, we apply this to one of the biggest sources of Compose jank: recomposition.
Frequently asked questions
Is the “Measuring Performance” lesson free?
Yes — the full text of “Measuring Performance” is free to read here on the web, and the Android Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Android Academy course, upgrade to CoddyKit PRO.
What will I learn in “Measuring Performance”?
Profilers, traces and benchmarks. You practise Android Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Android Academy?
No prior experience is required. Android Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Measuring Performance” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Android Academy lesson?
Yes. Every Android Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Measuring Performance
- Taming Recomposition
- Memory Leaks and Fixes
- Startup and Baseline Profiles