R8 & measuring startup

Shrink for release, measure the release build

R8 is the compiler that processes your release build: it removes code nothing reaches (dead-code elimination), inlines small functions, and renames classes/methods/fields to shorter obfuscated names β€” all of which shrinks the APK and can speed up execution. It runs automatically on a release build once minifyEnabled true is set in Gradle.

R8's aggressiveness is also its danger: reflection, JNI, and serialization access classes by name at runtime in ways R8's static analysis can't see. Without a keep rule (a ProGuard/R8 rule saying "never remove or rename this"), R8 can strip something that's only reached reflectively β€” and the app crashes only in release, never in debug.

This is also why you always benchmark the release build, never debug: a debug build skips R8 entirely and includes extra instrumentation, so its startup time is meaningfully slower and unrepresentative of what a real walker experiences. Measuring debug tells you nothing true about production performance.

To measure startup itself, Macrobenchmark again β€” this time with StartupTimingMetric(), which records time-to-initial-display across repeated cold (or warm/hot) launches, run through measureRepeated.