What counts as a slow start on Android?
| Start type | Counted as slow at |
|---|---|
| Cold | 5 seconds or more |
| Warm | 2 seconds or more |
| Hot | 1 second or more |
Measuring Startup with Macrobenchmark
StartupTimingMetric reports timeToInitialDisplayMs and, when the app reports fully drawn, timeToFullDisplayMs.@RunWith(AndroidJUnit4::class)
class StartupBenchmark {
@get:Rule
val benchmarkRule = MacrobenchmarkRule()
@Test
fun coldStartup() = benchmarkRule.measureRepeated(
packageName = "com.example.app",
metrics = listOf(StartupTimingMetric()),
startupMode = StartupMode.COLD,
iterations = 10,
) {
pressHome()
startActivityAndWait()
}
}
// In the first screen, once it is usable:
class MainActivity : ComponentActivity() {
private fun onContentReady() {
reportFullyDrawn() // Without this there is no TTFD
}
}Baseline Profiles: Compile the Startup Path Before First Launch
// app/build.gradle.kts
plugins {
id("com.android.application")
id("androidx.baselineprofile")
}
dependencies {
implementation("androidx.profileinstaller:profileinstaller:1.4.1")
baselineProfile(project(":baseline-profile"))
}Lazy Initialization with the App Startup Library
tools:node="remove" on the initializer's entry, and then call AppInitializer at the point of first use. The old sample instead posted the call to the main thread's handler from Application.onCreate() and described that as running after the first frame. It left the component registered for automatic startup, so nothing was deferred.
The current release is androidx.startup:startup-runtime:1.2.0.// An initializer: what to create, and what it depends on
class CrashReportingInitializer : Initializer<CrashReporter> {
override fun create(context: Context): CrashReporter {
return CrashReporter.init(context)
}
override fun dependencies(): List<Class<out Initializer<*>>> {
// Initialized first, automatically
return listOf(AnalyticsInitializer::class.java)
}
}
// AndroidManifest.xml: take it out of automatic startup
//
// <provider
// android:name="androidx.startup.InitializationProvider"
// android:authorities="${applicationId}.androidx-startup"
// android:exported="false"
// tools:node="merge">
// <meta-data
// android:name="com.example.CrashReportingInitializer"
// tools:node="remove" />
// </provider>
// At the first place the component is needed
AppInitializer.getInstance(context)
.initializeComponent(CrashReportingInitializer::class.java)Reducing Work Before the First Frame
when without binding it first. Why a screen recomposes more than it should after that first frame is a separate subject, in the Compose performance guide.@Composable
fun HomeScreen(viewModel: HomeViewModel) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
Column {
// Draws on the first frame, needs no data
TopBar(title = "Home")
when (val state = uiState) {
is HomeState.Loading -> LoadingPlaceholder()
is HomeState.Success -> {
LazyColumn {
items(state.items) { item ->
ContentCard(item)
}
}
}
is HomeState.Error -> ErrorBanner(state.message)
}
}
}Diagnosing Bottlenecks with a Trace
Application.onCreate() is exactly that.Key Takeaways
- 1Android vitals counts a cold start as slow at 5 seconds or more, a warm start at 2 and a hot start at 1. Those are ceilings, not targets.
- 2TTID and TTFD are equally important. TTFD only exists if the app calls reportFullyDrawn().
- 3Measure with Macrobenchmark before and after every change, on a release-like build.
- 4Baseline Profiles improve code execution speed by about 30% from the first launch, per Android. Add a startup profile as well.
- 5With App Startup, remove an initializer from the manifest before initializing it lazily, or nothing is deferred.
- 6Record a system trace and read the main thread up to the first frame. Use StrictMode to catch disk and network access there.
Frequently Asked
What is a good cold start time on Android?
Google publishes a ceiling, not a target. Android vitals counts a cold start as slow at 5 seconds or more, a warm start at 2 seconds or more and a hot start at 1 second or more. Treat those as the point of failure, set your own budget well below them, and measure it with Macrobenchmark.
How do Baseline Profiles help startup?
They list the code paths to compile ahead of time, so startup code is not interpreted and JIT-compiled while the user waits. Android states that Baseline Profiles improve code execution speed by about 30% from the first launch for included code paths, and recommends adding a startup profile to fully optimize app startup.
What is the difference between TTID and TTFD?
Time to initial display is the time it takes to display the first frame, and the Android Framework reports it for every app. Time to full display is the time until the app is fully interactive, and it is only recorded when the app calls reportFullyDrawn(). Android describes the two as equally important.
Ready to architect your next Android app?
ANDROID-ARCHITECT generates production-ready Kotlin code, architecture blueprints, and CI/CD configurations from plain-language descriptions. Start building for free.