androidengineers.Book a session

Performance Optimization

Startup Optimization: App Startup API

article25 minHard

Cold start time is one of the most-watched metrics in mobile: Google Play uses it in app quality ratings, and users abandon apps that take more than 2 seconds to show content. This lesson covers the tools and techniques to measure and improve it.

Startup Timeline

User taps icon
  └── Zygote fork            (~5–10ms, not controllable)
  └── attachBaseContext
  └── ContentProvider.onCreate (ALL providers, synchronous)
  └── Application.onCreate
  └── Activity.onCreate
  └── Activity.onStart/onResume
  └── First frame rendered   ← TTID (Time to Initial Display)
  └── Content loaded         ← TTFD (Time to First Draw)

Everything between process creation and first frame is "startup." Most optimization happens in ContentProvider.onCreate and Application.onCreate.

Measuring Startup

# Method 1: adb am start -W
adb shell am force-stop com.example.myapp
adb shell am start-activity -W -n com.example.myapp/.MainActivity
# Output: TotalTime: 1234ms  ← this is your baseline

# Method 2: logcat
adb logcat | grep "Displayed"
# I/ActivityTaskManager: Displayed com.example.myapp/.MainActivity: +1s234ms

Also check Android Vitals in Play Console — real user cold start P50/P95 percentiles.

App Startup Library

Jetpack's App Startup library replaces multiple ContentProviders (each adds ~10–30ms overhead) with a single provider that initializes all your components lazily or eagerly via Initializer:

// Before: each SDK has its own ContentProvider — 3 providers = ~60ms overhead
// WorkManager's old approach, Firebase's approach, etc.

// After: all initializers share one ContentProvider
class WorkManagerInitializer : Initializer<WorkManager> {
    override fun create(context: Context): WorkManager {
        val config = Configuration.Builder().setMinimumLoggingLevel(Log.ERROR).build()
        WorkManager.initialize(context, config)
        return WorkManager.getInstance(context)
    }

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

class AnalyticsInitializer : Initializer<AnalyticsClient> {
    override fun create(context: Context): AnalyticsClient {
        return AnalyticsClient.init(context)
    }
    override fun dependencies() = emptyList<Class<out Initializer<*>>>()
}
<!-- AndroidManifest.xml — replaces multiple SDK ContentProviders -->
<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false">
    <meta-data
        android:name="com.example.WorkManagerInitializer"
        android:value="androidx.startup" />
    <meta-data
        android:name="com.example.AnalyticsInitializer"
        android:value="androidx.startup" />
</provider>

Lazy Initialization

If a component isn't needed until the user interacts, don't initialize it at startup:

// ❌ Initialized unconditionally at startup
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        HeavySdk.initialize(this)  // ~200ms, always paid
        Analytics.init(this)       // ~100ms, always paid
    }
}

// ✅ Lazy — initialized on first access
class MyApplication : Application() {
    val heavySdk: HeavySdk by lazy { HeavySdk.initialize(this) }
    // Analytics only initialized when user triggers a tracked event
}

Defer Non-Critical Work

// Defer work that doesn't affect first frame
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        // Critical: must run synchronously
        CrashReporting.init(this)

        // Non-critical: defer to after first frame
        lifecycleScope.launch {
            // Wait until the main Activity is resumed
            ProcessLifecycleOwner.get().lifecycle.withResumed {
                Analytics.init(this@MyApplication)
                PushNotifications.init(this@MyApplication)
            }
        }
    }
}

ProfileInstaller (Baseline Profiles)

Baseline Profiles pre-compile the hot paths in your app so ART doesn't JIT-compile them at first run:

// build.gradle.kts
dependencies {
    implementation("androidx.profileinstaller:profileinstaller:1.3.1")
}

Generate with Macrobenchmark:

@RunWith(AndroidJUnit4ClassRunner::class)
class BaselineProfileGenerator {
    @get:Rule
    val rule = BaselineProfileRule()

    @Test
    fun generate() = rule.collect("com.example.myapp") {
        pressHome()
        startActivityAndWait()
        // Navigate through critical flows
    }
}

Common Startup Bottlenecks

BottleneckTypical costFix
Firebase init ContentProvider50–100msDisable auto-init; use lazy init
Room.build() on main thread50–80msBuild DB on background thread
Network call in Application.onCreateVariableNever do this
SharedPreferences on main thread10–50msUse DataStore with coroutines
Large dex class loading20–100msBaseline Profiles

YOUR LEARNING JOURNEY

0 of 177 available lessons completed

Progress saved in this browser. No account needed.
Startup Optimization: App Startup API | Android System Design | Android Engineers