androidengineers.Book a session

Performance & Internals

App Startup Optimization

article40 minMedium

App startup time directly affects user retention. Google's internal research shows that apps taking more than 5 seconds to start lose a significant percentage of users. There is a direct relationship between startup time and Play Store ratings.

Understanding the three start types and where time is spent gives you a concrete plan for improvement.

Three Start Types

Cold start: The process does not exist. Android creates a new process, initializes the Application class, creates the first Activity, and draws the first frame. This is the slowest start. Optimization here has the most user impact.

Warm start: The process exists but the Activity was destroyed (e.g. the user navigated away). Android recreates the Activity but skips process creation.

Hot start: The process and Activity both exist (the user just switched away). Android brings the window to the foreground. Optimization here has minimal impact.

Measuring Startup

Never optimize what you cannot measure.

Reported Display Time — Android logs the time from process creation to first drawn frame:

ActivityTaskManager: Displayed com.example/.MainActivity: +1s234ms

Macrobenchmark — The recommended way to measure startup in CI:

@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()
    }
}

Run this on a real device (not an emulator) for accurate numbers.

What Makes Startup Slow

  1. Heavy Application.onCreate() — Initializing analytics SDKs, crash reporters, and third-party libraries synchronously on the main thread.

  2. Slow ContentProvider initialization — Content providers are initialized before Application.onCreate. Hidden initializations via Startup library or library-provided providers add time invisibly.

  3. Main thread I/O — Reading from SharedPreferences, database, or network on the main thread.

  4. Long inflation/composition — Complex first screen with many composables or deeply nested views.

Lazy Initialization

Move non-critical work off the startup path.

class App : Application() {

    override fun onCreate() {
        super.onCreate()
        // Critical: must be ready before any screen shows
        initializeCrashReporter()

        // Defer: analytics can wait until the user interacts
        MainScope().launch {
            delay(3000)
            initializeAnalytics()
        }
    }
}

Use by lazy for singletons that are not always needed:

val heavyClient: HeavyApiClient by lazy { HeavyApiClient.create() }

App Startup Library

The App Startup library provides a structured way to initialize components in sequence with dependency management, replacing multiple ContentProvider registrations.

class AnalyticsInitializer : Initializer<Analytics> {
    override fun create(context: Context): Analytics {
        return Analytics.initialize(context)
    }

    override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()
}

Declare in the manifest:

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup">
    <meta-data
        android:name="com.example.AnalyticsInitializer"
        android:value="androidx.startup.Initializer" />
</provider>

To defer an initializer, call AppInitializer.getInstance(context).initializeComponent(AnalyticsInitializer::class.java) when you actually need it.

Baseline Profiles

Baseline Profiles tell the Android Runtime (ART) which code paths to ahead-of-time compile before the user ever runs them. This eliminates JIT compilation overhead on first run.

Generate a Baseline Profile with the Macrobenchmark library:

@RunWith(AndroidJUnit4::class)
class BaselineProfileGenerator {

    @get:Rule
    val baselineRule = BaselineProfileRule()

    @Test
    fun generate() = baselineRule.collect(
        packageName = "com.example.app"
    ) {
        pressHome()
        startActivityAndWait()
        // walk critical user journeys
    }
}

The generated baseline-prof.txt file goes in src/main/. The AGP bundles it into the release APK/AAB. Google Play compiles these methods during installation, so users get fast startup on first launch.

Splash Screen API

Use the SplashScreen API (API 31+, backwards-compatible via core-splashscreen) instead of a custom launch Activity. It shows the app icon on the system-drawn splash, hides it when the first frame is drawn, and avoids a full extra Activity in the back stack.

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        val splashScreen = installSplashScreen()
        super.onCreate(savedInstanceState)

        // Keep splash visible while data loads
        splashScreen.setKeepOnScreenCondition { viewModel.isLoading.value }

        setContent { App() }
    }
}

Common Wins Summary

OptimizationTypical gain
Defer analytics SDK init200–600 ms
Baseline Profiles20–40% on first cold start
Lazy heavy singletons100–400 ms
Replace ContentProviders with App Startup50–200 ms
Move SharedPreferences reads off main thread50–300 ms

Practice

Profile your app's cold start with the Macrobenchmark library. Then add a method trace in Application.onCreate() using Trace.beginSection and Trace.endSection to find the slowest initializer. Move it to a lazy {} block and measure again.

Summary

Optimize cold start by deferring non-critical work, using Baseline Profiles for ART compilation, moving I/O off the main thread, and measuring with Macrobenchmark rather than eyeballing. Every 100ms you save reduces churn, especially on low-end devices.

YOUR LEARNING JOURNEY

0 of 17 available lessons completed

Progress saved in this browser. No account needed.
App Startup Optimization | Senior Android Developer | Android Engineers