R8 8 min read

Grab Cut ANRs 32% and Startup 35% with R8 Full Mode and Baseline Profiles: The Playbook

Most R8 write-ups stop at "enable minification, ship it." Grab's case study, published on the Android Developers Blog on October 19, 2026, is more useful because it covers what happens when you do this on a roughly ten-year-old codebase with over 10 million lines of code and hundreds of third-party libraries. The headline: 32% fewer ANRs, about 30% smaller downloads, and 35% faster startup, reached in phases over more than a year.

The numbers are good, but the sequencing and the unglamorous operational work behind them are what you can copy. Here is the breakdown.

~32.5%
Cumulative reduction in user-perceived ANRs
~30.4%
Cumulative download size reduction
~35%
Faster cold start across all phases

The Starting Point

Grab is a Southeast Asian superapp covering deliveries, mobility, and financial services across eight countries. It uses a hybrid build: Bazel for debug and Gradle with R8 for release. The team was fighting three problems at once: high ANR rates, an app that grew with every new service, and startup time that rose year over year.

The ANR pattern was specific. Spikes followed each weekly release, and the main cause was the overhead of reading many DEX files during initialization. Google Play Cloud Profiles covered standard flows but not every initialization path, such as Dagger dependency graph setup at launch. Code outside the profile ran through JIT compilation and background optimization over the following days, which is exactly when users saw ANRs.

The fix was a custom Baseline Profile that fed Grab's critical user journeys into Play's Cloud Profiles, so those paths were compiled to machine code at install time.

Phase 1: R8 Optimization in Compat Mode (2025)

The first step was unglamorous. Grab moved from the legacy default ProGuard file to proguard-android-optimize.txt, which turns on R8's optimizations. They also cleaned up legacy configuration and reflection usage across the codebase, and shipped Baseline Profiles and Startup Profiles in the same release.

android {
    buildTypes {
        release {
            isMinifyEnabled = true
            isShrinkResources = true
            proguardFiles(
                // the optimizing default, not the legacy proguard-android.txt
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

Results for this phase: about 25% fewer user-perceived ANRs, a 16% smaller download, and about 27% faster cold start. Grab attributes 12% of the startup gain to R8 and 15% to Baseline Profiles, a useful reminder that the two tools stack rather than overlap.

Phase 2: R8 Full Mode (2026)

Nine months later Grab moved to R8 Full Mode. Libraries such as Retrofit, Firebase, and Jetpack Compose worked with their built-in consumer rules, and Grab adopted the consumer keep rules from the latest Gson release for JSON parsing.

The real breakage came from Java dynamic proxies. R8 can't see runtime reflection, so it strips or renames the classes involved and the app crashes at runtime. The tempting fix is a broad exception like -keep class com.yourapp.** { *; }, which defeats the optimization and bloats the app. Grab did the opposite: isolate the specific reflection-dependent classes and write targeted keep rules.

# Illustrative: keep only what reflection actually touches
-keep interface com.example.api.PaymentsApi
-keepclassmembers class com.example.model.PaymentRequest {
    <fields>;
}

The post does not publish Grab's actual rules, so treat the snippet above as the shape of the approach rather than their config. Results for Full Mode: an additional ~14% smaller download, ~10% faster startup, and ~10% fewer ANRs. After the migration, the R8 Configuration Analyzer scored the app at 72% for optimization, 73% for shrinking, and 79.6% for obfuscation.

Compounding, not adding: the cumulative figures match multiplying the phases. For ANRs, 1 − (0.75 × 0.90) ≈ 32.5%. For size, 1 − (0.84 × 0.86 × 0.964) ≈ 30.4%. Don't add percentages from separate rollouts when you report your own results.

Phase 3: Keep-Rule Hygiene (In Progress)

Years of accumulated keep rules quietly cap what R8 can do. Grab is using the R8 Configuration Analyzer to find and trim legacy rules, targeting scores of 78.2% for optimization, 79.1% for shrinking, and 81.7% for obfuscation, plus about 3.6% additional download size reduction. Note that these are in-progress targets, not shipped results.

The planned follow-up is the part most teams skip: pre-merge CI guardrails where any new keep rule needs explicit justification and review, with analyzer scores tracked in CI as a regression signal. Without that gate, the rule file grows back.

Results at a Glance

PhaseANRsSizeCold start
R8 optimizer (2025)~25% fewer16% smaller~27% faster
R8 Full Mode (2026)10% fewer14% smaller10% faster
Keep-rule cleanup—3.6% smaller—
Cumulative~32.5%~30.4%~35%

The Operational Lessons

The most transferable parts of the post are not R8 flags. They are how a lean team made an aggressive optimizer safe to ship.

What the post doesn't tell you: it shows no keep rules, no Baseline Profile contents, and little detail on Startup Profile or DEX layout work. It also uses slightly different figures for the size reduction in different sections (about 30%, 30.4%, and 30.3%). Use it as a roadmap, not a config to copy.

How I'd Apply This to a Smaller App

Most apps are nowhere near Grab's scale, but the order of operations holds up. This is my own take, not something from the case study.

The takeaway is that R8 is not a flag you flip once. The 35% startup improvement came from two migrations, a profile pipeline, and a process that keeps the rule file honest.

Source: Grab Reduces ANRs by 32% and improves startup time by 35% with R8 and Profile guided optimization, Android Developers Blog, October 19, 2026.

Comments 0

No comments yet. Be the first to leave one!

Leave a comment