Your baseline-prof.txt is committed, CI runs the generator task every release, and the dashboard shows a nice fat profile file in the APK. Cold start is still slow. Here's the thing almost nobody checks: a baseline profile can be present, committed, and completely inert — and your build will never tell you.
The three ways a baseline profile silently no-ops
1. It was generated against the wrong build variant
BaselineProfileRule from Macrobenchmark has to run against a build that's shaped like what you ship — R8-minified, same signing config path as release (or a benchmarkRelease variant that mirrors it). Run the generator against plain debug, and the method/class signatures it captures are the pre-obfuscation names. R8 renames com.yourapp.feature.checkout.CheckoutViewModel to something like com.yourapp.a.b.c in the shipped build. The profile rules reference symbols that don't exist in the APK you actually install. ART's profile installer loads it, finds nothing matches, and silently treats it as empty.
// build.gradle.kts (benchmark module)
android {
testBuildType = "benchmarkRelease" // NOT "debug"
}
@RunWith(AndroidJUnit4::class)
class BaselineProfileGenerator {
@get:Rule
val rule = BaselineProfileRule()
@Test
fun generate() = rule.collect(
packageName = "com.yourapp",
maxIterations = 15,
) {
pressHome()
startActivityAndWait()
// exercise the actual critical user journey, not just launch
device.findObject(By.text("Checkout")).click()
device.wait(Until.hasObject(By.res("checkout_summary")), 5_000)
}
}
If testBuildType isn't pointed at a release-shaped build, everything downstream is wasted effort.
2. The profile is stale relative to the screens you shipped
Baseline profiles get baked into the APK at build time from whatever .pb / .prof file sits in src/main/baselineProfiles/. They are not regenerated automatically when you add a new Compose screen or a new navigation destination. Ship a new checkout flow without regenerating, and that flow's composables get zero AOT compilation — they run interpreted, then JIT-warm over the next few invocations, which is the exact same user-visible jank as having no baseline profile at all for that screen. Nothing in your build fails; nothing warns you. The old profile is still "valid," it just doesn't cover the code path anyone's actually hitting on first run.
Treat profile regeneration as a release-checklist item tied to "did the critical path change," not a one-time setup task.
3. You never actually verified it installed
This is the one that costs the most time, because it's invisible from inside the app. Don't trust "the file is in the APK" as proof the profile is active — verify installation and compilation state directly:
# Confirms ART actually compiled against the profile, not just "verify" (interpreted)
adb shell dumpsys package compiler-stats com.yourapp
# Look for: reason=speed-profile
# "reason=verify" (or no entry at all) means the baseline profile had no effect
# ProfileInstaller logs the moment it writes the profile on first run
adb logcat -s ProfileInstaller:*
# Expect: "Installation of a profile requested" followed by a success line,
# not a skip/failure
If you're on Macrobenchmark, the cleanest signal is a before/after StartupTimingMetric comparison — same device, same journey, with and without the profile installed. If the numbers are within noise, something above silently failed; it's not that baseline profiles "don't help for your app."
Why this matters more than the profile-generation tutorials suggest
Every walkthrough covers step 1 (write the generator, run ./gradlew generateBaselineProfile) and stops. The actual cost shows up weeks later: a profile that was correct in week one quietly goes stale as the app evolves, and nobody re-checks compiler-stats because the task "already works." Cold-start regressions introduced this way are genuinely hard to diagnose from a bug report alone — the symptom (slow first launch on a new screen) looks identical to a dozen other causes.
If you're building out a performance-review habit around this — baseline profile freshness, recomposition stability, and the architectural decisions that make profiling easier instead of harder in the first place — that's exactly the kind of senior-level checklist covered in the Complete Android Engineer Bundle, alongside interview-style deep dives and the system design patterns that keep critical paths small enough to profile in the first place.
Top comments (0)