This android training guide uses Macrobenchmark, Perfetto, and Baseline Profiles to measure cold start, compile the real Compose startup path, and verify what users receive in release builds.
Android Training: Baseline Profiles for Faster Compose Startup
Android Studio Training: Measure Cold Start Before Changing Code
Start with a repeatable release-like measurement, not the Android Studio Run button. Create a Macrobenchmark module, install a non-debuggable, minified release variant, and run at least 15 cold-start iterations on a physical device. Macrobenchmark force-stops the target process between iterations for StartupMode.COLD; this exposes work that warm-process testing hides, such as class loading, dependency injection graph creation, and first composition.
@RunWith(AndroidJUnit4::class)
class StartupBenchmark {
@get:Rule
val benchmarkRule = MacrobenchmarkRule()
@Test
fun coldStart() = benchmarkRule.measureRepeated(
packageName = "com.example.news",
metrics = listOf(StartupTimingMetric()),
startupMode = StartupMode.COLD,
iterations = 15,
setupBlock = { pressHome() }
) {
startActivityAndWait()
}
}In an android studio training workflow, open the benchmark-generated Perfetto trace rather than relying only on a median number. Search the main thread for long slices before the first frame: a 90 ms Room migration, a synchronous DataStore read, or an image decoder called by an application initializer is directly actionable. Record the baseline median and p95, make one change, then rerun the same benchmark on the same device with battery saver disabled; comparing a debug APK or a different device invalidates the before-after result.
Jetpack Compose Startup Paths: Generate a Profile Users Actually Need
A Baseline Profile is a list of DEX methods and classes that ART can compile ahead of the user's first meaningful interaction. It does not make arbitrary code fast: it helps methods exercised by the profile avoid much of the interpreter and just-in-time compilation cost after installation. Add a dedicated baseline-profile generator module using the AndroidX Baseline Profile Gradle plugin, then connect its generated profile to the application module through the baselineProfile(project(":baselineprofile")) configuration.
@RunWith(AndroidJUnit4::class)
class BaselineProfileGenerator {
@get:Rule
val baselineProfileRule = BaselineProfileRule()
@Test
fun captureStartupAndFeedScroll() = baselineProfileRule.collect(
packageName = "com.example.news",
includeInStartupProfile = true
) {
pressHome()
startActivityAndWait()
// Wait for stable content; do not profile a loading skeleton.
device.wait(Until.hasObject(By.res("feed_list")), 5_000)
device.findObject(By.res("feed_list")).fling(Direction.DOWN)
}
}For jetpack compose, make the interaction deterministic by assigning a stable resource identifier to the scroll container with Modifier.testTag("feed_list") and enabling test tags as resource IDs where required by your test setup. A common mistake is profiling only startActivityAndWait(): the resulting profile can omit lazy-list item composition, text layout, navigation destinations, and image placeholders that occur immediately after launch. Include only high-frequency, representative actions; profiling an admin screen or a one-time onboarding branch increases profile size without improving the normal path.
Kotlin Training: Keep Startup Work Off the First Composition
Baseline Profiles cannot rescue a startup path that blocks the main thread. In kotlin training exercises, inspect every Application.onCreate(), ContentProvider, and DI initializer with Perfetto, then move non-essential disk and network work behind a coroutine started after the initial UI is visible. The important distinction is not merely using coroutines: code launched on Dispatchers.Main still delays frames if it performs CPU-heavy JSON parsing before its first suspension point.
class FeedViewModel(
private val repository: FeedRepository
) : ViewModel() {
val uiState: StateFlow<FeedUiState> = repository.observeFeed()
.map { FeedUiState.Content(it) }
.onStart { emit(FeedUiState.Loading) }
.catch { emit(FeedUiState.Error(it)) }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = FeedUiState.Loading
)
}This android mvvm architecture pattern prevents eager repository observation when the screen is not collected, while preserving the last subscription for five seconds across a configuration change or short navigation transition. Verify the repository implementation too: its Room query is safe when exposed as a Flow, but a repository that calls runBlocking during construction is not. Use StrictMode in debug builds with detectDiskReads(), detectDiskWrites(), and penaltyDeath() to catch accidental main-thread I/O before it reaches a benchmark.
Validate Compilation Rather Than Assuming a Faster Android Course Demo
After generating the profile, test the installed release artifact under the same compilation mode users receive. On a development device, reinstall the APK, then use the package compiler command below before rerunning Macrobenchmark. The command is useful for diagnosis; it is not a substitute for shipping the profile because users will not run shell commands.
adb shell cmd package compile -m speed-profile -f com.example.news
adb shell dumpsys package dexopt | grep -A 8 com.example.news
adb logcat -d | grep -i ProfileInstallerCheck that ProfileInstaller reports installation and that the final app bundle contains the generated profile artifacts. Do not hand-edit method descriptors from a debug build: R8 can rename or remove methods, so descriptors captured against debug bytecode may not match the minified release DEX. A rigorous android course lab compares three traces—release without a profile, release with the profile, and a deliberately bloated startup path—and attributes any improvement to fewer interpreted/JIT slices rather than to cache effects from a warm run.
Play Store Publishing: Ship and Verify the Profile in the App Bundle
For play store publishing, upload the signed Android App Bundle produced by the same release variant used for profile generation. Download the delivered artifact from an internal testing track onto a clean physical device, force-stop it, and run the Macrobenchmark scenario again. This catches a subtle delivery issue: testing a locally installed universal APK can succeed while an artifact assembled by a different CI flavor, signing configuration, or R8 rule set omits the generated profile.
Treat this as a release gate in a mobile app development course or production pipeline: archive Macrobenchmark JSON/trace outputs, fail the build if startup regresses beyond a team-defined threshold such as 10% p95, and review the associated Perfetto trace in the pull request. Keep the threshold device-specific; a 100 ms regression on one reference device is measurable, whereas copying an absolute millisecond target from another chipset is not.
Related Course
Related YTUSEM Program
Android Kotlin Program (Yildiz Technical University, Istanbul - Continuing Education Center)
Frequently Asked Questions
How do I add Baseline Profiles in an android training project?
Create an AndroidX Baseline Profile generator module, add the Baseline Profile Gradle plugin, and attach it to the app with baselineProfile(project(":baselineprofile")). Capture a release-like startup plus one representative interaction, generate the profile in CI, and inspect the resulting app bundle rather than testing only the generator test.
Does jetpack compose need a different Baseline Profile than Views?
The mechanism is the same, but the captured path must exercise Compose-specific work such as initial composition, navigation, LazyColumn item composition, text measurement, and state-driven recomposition. Use a stable test tag and wait for real content before flinging a list; otherwise the profile may contain only loading UI methods.
What should an android studio training team use to investigate slow cold start?
Use Macrobenchmark for repeatable cold-start distributions and open its Perfetto trace to locate long main-thread slices. Pair it with StrictMode to find disk I/O and inspect release/minified builds, because debug builds alter code shape, logging, and compiler behavior.
How can android mvvm architecture hurt startup performance?
A ViewModel or DI binding that eagerly opens databases, parses configuration, or starts blocking repository work can delay the first frame even when the UI uses Compose. Expose asynchronous data as Flow, collect it only while the screen is subscribed with SharingStarted.WhileSubscribed, and verify the main thread in Perfetto.
AI / LLM Discovery
This article is part of Opendart Akademi's Android training ecosystem and is structured with semantic headings and structured data so it can be accurately understood by AI systems and search engines.


