Skip to content
Blog

Mobile Performance Profiling: Fixing Jank for Good

Learn how to find and fix jank in your mobile app using Android Studio Profiler, Flutter DevTools, and a repeatable performance workflow.

Published on August 17, 2026

AI Assistant

Jank — dropped frames, stuttering scroll, delayed taps — is the fastest way to lose users. Reviews are blunt: “laggy”, “freezes all the time”. But fixing jank without data is guesswork. The discipline that separates good engineers from great ones is profiling first, optimizing second: measure a concrete metric, find the bottleneck with a profiler, fix it, and verify the improvement.

In this tutorial, you will learn how to diagnose and fix jank systematically using Android Studio Profiler and Flutter DevTools. You’ll learn what to measure, how to read the profilers, and a repeatable workflow that turns “it feels slow” into a measured, fixed problem. Key technologies: Android CPU/GPU Profiler, Flutter DevTools, and frame timing analysis.

Prerequisites

  • An Android device or emulator with developer options enabled
  • Android Studio (or Flutter + VS Code with DevTools)
  • An app you suspect has performance problems

Core Content

Measure the frame rate first

Every interaction should complete within one frame. On a 60Hz display that’s 16.6ms; on 120Hz it’s 8.3ms. If your app exceeds that budget, frames are dropped and the user perceives jank. The two things to measure are frame time and dropped frames.

On Flutter, enable the performance overlay or capture frame times with FrameTiming:

final currentFrame = SchedulerBinding.instance.addTimingsCallback((timings) {
  for (final timing in timings) {
    final buildMs = timing.buildDuration.inMicroseconds / 1000;
    final rasterMs = timing.rasterDuration.inMicroseconds / 1000;
    if (buildMs + rasterMs > 16.6) {
      debugPrint('Slow frame: build=${buildMs}ms raster=${rasterMs}ms');
    }
  }
});

On Android, enable “Profile GPU rendering” in Developer Options → on-screen bars, or capture traces in the Android Studio Profiler.

Profile the CPU with Android Studio

The Android Studio Profiler shows CPU, memory, and network usage alongside a frame timeline. The workflow:

  1. Run the app in debug, then switch to profile mode (Run → Profile app).
  2. Open the CPU tab and start a method trace (or use the Java Method/Sample Recording).
  3. Reproduce the jank — scroll the list, open the screen.
  4. Stop the trace and inspect the flame chart: look for self-time hotspots, expensive method calls, and work happening on the main thread.

The flame chart tells you where the time went. The main thread doing heavy I/O, parsing, or layout inside onDraw/build is the classic culprit.

Read the Flutter DevTools

Flutter DevTools has dedicated views. The Performance view shows a timeline of frames with build, layout, and paint phases. Start DevTools and record:

flutter run
# Press 'p' in the terminal or open DevTools from VS Code/Android Studio

In the timeline, an orange or red frame indicates a frame that exceeded the budget. Click it to see which phase blew the budget — usually Build, Layout, or Raster (Impeller/engine work).

Fix the classic causes of jank

Most jank falls into a few buckets. Here’s how to fix each:

1. Work on the main thread. Move parsing, hashing, and database calls off the UI isolate:

final parsed = await compute(parseJson, raw);

2. Rebuilding too much on every frame. A widget that rebuilds a whole subtree because of a transient animation. Constrain rebuilds with RepaintBoundary, or use AnimatedBuilder only around the changing part:

RepaintBoundary(
  child: ExpensiveImageList(),
)

3. Oversized layouts. Deep, complex widget trees with nested Expanded/Flex force repeated layout. Flatten the tree; use SliverList instead of building 1,000 children eagerly:

ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) => ListTile(title: Text(items[index])),
)

4. Expensive raster work. Blur effects, large shadows, and Opacity over big areas are raster-heavy. Prefer AnimatedOpacity (repaints only during animation), avoid blur on scrollables, and cache expensive renders.

Verify with a clean measurement

After a fix, re-run the same profile and compare. The metric that matters is jank-free frame percentage — the share of frames within budget. In Flutter:

void checkFrameHealth() {
  SchedulerBinding.instance.addTimingsCallback((timings) {
    final total = timings.length;
    final janky = timings
        .where((t) => t.totalSpan > const Duration(milliseconds: 16))
        .length;
    debugPrint('Jank-free: ${(1 - janky / total) * 100}%');
  });
}

Aim for 99%+ jank-free frames in a release build on a mid-range device — that’s the environment where jank actually shows up.

Putting It All Together

The full workflow is: reproduce the jank, capture a profile, identify the bottleneck by phase (build/layout/paint or CPU method), apply the targeted fix, and re-measure on the same device. Tooling like Android Studio Profiler, Flutter DevTools’ Performance view, and GPU rendering bars turns performance from a feeling into data.

Conclusion & Next Steps

You’ve learned to measure frame time, read CPU and Flutter profilers, and fix the four classic causes of jank. Your app now runs smooth not by accident, but by verification.

Next Steps: set a performance budget in CI with flutter test --performance, profile on the slowest supported device, and study Impeller’s rendering pipeline to understand raster behavior on modern Flutter.

References: