Profiling and Optimizing Android App Performance

Performance optimization without measurement is guesswork. This article covers the tools and techniques that actually move the needle — from diagnosing startup time to eliminating jank in scrolling lists.

Step 1: Measure First

Before changing any code, establish a baseline with reproducible benchmarks. The Macrobenchmark library lets you measure startup time, scrolling performance, and custom scenarios in a controlled way.

@RunWith(AndroidJUnit4::class)
class StartupBenchmark {
    @get:Rule
    val benchmarkRule = MacrobenchmarkRule()

    @Test
    fun startup() = benchmarkRule.measureRepeated(
        packageName = "com.example.app",
        metrics = listOf(StartupTimingMetric()),
        iterations = 5,
        startupMode = StartupMode.COLD
    ) {
        pressHome()
        startActivityAndWait()
    }
}

App Startup Optimization

  • Baseline Profiles — provide a baseline-prof.txt file that guides ART to AOT-compile your hot code paths. Can reduce cold start by 30–40%.
  • Lazy initialization — defer anything not needed on the first frame. Use App Startup library to sequence initializers.
  • Avoid heavy work on the main thread — database reads, SharedPreferences access, and network calls must all move off the main thread.

Eliminating Jank (Dropped Frames)

The Android Profiler's CPU view and the Systrace tool reveal exactly where frames drop. Common causes:

  • Layout inflation during scrolling — use RecyclerView prefetching or Compose's LazyColumn with stable keys.
  • Large bitmaps decoded on the main thread — use Coil or Glide which decode off-thread by default.
  • Expensive onDraw() implementations — avoid object allocation inside draw calls.
  • Nested ConstraintLayout or deep view hierarchies — flatten with ConstraintLayout or migrate to Compose.

Memory Optimization

  • Use the Memory Profiler to spot allocation spikes and leaks. Look for retained objects in the heap dump.
  • Avoid static references to Context or Views — they prevent garbage collection.
  • Use WeakReference only when strictly necessary; prefer lifecycle-aware components instead.

Network and Battery

  • Batch network requests and use caching (OkHttp cache or Room) to avoid redundant calls.
  • Respect the system's battery optimization mode — check PowerManager.isPowerSaveMode() and reduce background work accordingly.
  • Use WorkManager constraints (requiresCharging, requiresNetworkType) to schedule expensive work intelligently.

Key Tools

  • Android Studio Profiler — CPU, memory, network, and energy in one place.
  • Perfetto — detailed system-level traces.
  • Macrobenchmark — reliable startup and scroll benchmarks.
  • LeakCanary — automatic memory leak detection in debug builds.