On this page
Every SDK added "just one line" to Application.onCreate(). Two years later, cold start takes three seconds on mid-range phones. Startup regressions creep in silently, so you need measurement, a budget, and a few structural fixes.
Cold, warm and hot starts
| Type | What happens | Cost |
|---|---|---|
| Cold | Process created, Application and Activity created, first frame drawn | Highest: optimise this |
| Warm | Process exists, but the Activity is recreated | Medium |
| Hot | Activity brought to the foreground | Lowest |
Metrics: TTID and TTFD
- Time to Initial Display (TTID): until the first frame is drawn. Logcat prints it as "Displayed โฆ".
- Time to Full Display (TTFD): until the content the user cares about is shown. Report it by calling
reportFullyDrawn(), or with Compose'sReportDrawnWhen.
@Composable
fun HomeScreen(state: HomeUiState) {
ReportDrawnWhen { state is HomeUiState.Content } // tells the system when real content is visible
// ...
}Measuring reliably
- Macrobenchmark with
StartupTimingMetricon a release-like build. See the performance guide for code. - System traces (Perfetto or the Android Studio Profiler) to see what runs during startup.
- Android vitals for real-user startup data.
The highest-impact fixes
1. Slim down Application.onCreate()
@HiltAndroidApp
class App : Application() {
@Inject lateinit var crashReporting: CrashReporting // keep: needed early
@Inject lateinit var analytics: dagger.Lazy<Analytics> // defer: created on first use
@Inject lateinit var appScope: CoroutineScope // injected application scope
override fun onCreate() {
super.onCreate()
crashReporting.init()
appScope.launch(Dispatchers.Default) {
delay(1_000) // after the first screen settles
analytics.get().init()
RemoteConfigWarmup.fetch()
}
}
}Ask of every initialiser: does the first screen need it? If not, defer it, make it lazy, or initialise it on first use.
2. Watch hidden initialisers
Some libraries initialise themselves through ContentProviders that run before Application.onCreate(). The App Startup library lets you consolidate initialisers into one provider, control dependencies between them, and disable automatic initialisation for libraries you want to start lazily.
3. Ship a Baseline Profile
Baseline Profiles pre-compile the code on your startup path. They're often one of the biggest single wins, especially for Compose apps. See Baseline Profiles.
4. Make the first screen cheap
- Render cached data or a lightweight skeleton immediately. Load the rest asynchronously.
- Avoid synchronous disk reads, such as large SharedPreferences, before the first frame.
- Keep the first composition small. Defer heavy, below-the-fold UI.
5. Use the SplashScreen API correctly
class MainActivity : ComponentActivity() {
private val viewModel: StartupViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
val splash = installSplashScreen()
super.onCreate(savedInstanceState)
// Keep the splash only until the minimum state (e.g. auth check from local storage) is ready
splash.setKeepOnScreenCondition { !viewModel.isReady.value }
setContent { AppRoot() }
}
}Don't keep the splash visible for network calls or artificial delays. That only hides a slow start.
Typical wins
SDK diet
Deferring analytics, ads and remote config initialisation often removes a large share of Application.onCreate time.
Baseline Profile
A measurable startup improvement with little code change. Verify it with Macrobenchmark.
Lazy DI graph
Lazy<T> and providers stop the whole object graph being created at launch.
Common mistakes
- Measuring debug builds, or only on flagship phones.
- Moving work to a background thread that the first screen then waits for anyway.
- Blocking on network or remote config before showing anything.
- No regression guard. Add a startup benchmark to CI.
Frequently asked questions
What's a good cold start time?
Faster is better, and Android vitals flags excessive startup times. Set a budget for your app on a mid-range device and track it over releases.
Does Hilt slow startup?
Hilt itself is fast (code generated at compile time), but eagerly creating many singletons can. Use lazy injection for non-critical dependencies.
Should I use multidex or other old tricks?
Modern minSdk levels support multidex natively. Focus on initialisation work and Baseline Profiles.
Conclusion
Faster startup comes from measuring cold starts on release builds, deferring everything the first screen doesn't need, controlling library initialisers, shipping a Baseline Profile, and keeping the first screen light, then guarding it with a benchmark.
Next step: go deeper on the biggest single lever in Baseline Profiles and App Performance.
Official references
๐ฑ Apps built by Yunok Tech
Real Android apps on Google Play, free to download.
Walzi4K wallpapers for lock and home screenGet it โธ
NotiVaultNotification history: recover dismissed notificationsGet it โธ
Ready to build real Android applications?
Performance work starts with understanding how Android apps are built and how they behave at runtime. Strengthen those fundamentals and the profiling tools start to make sense.
Move beyond theory and strengthen your Android development skills with Yunoktech's Android course. Learn core concepts, practical implementation, modern Android development techniques, and the reasoning behind building maintainable applications.
Explore the Android course and take your next step as an Android developer.