How R8 Works

How R8 Shrinks, Obfuscates and Optimizes

R8 234 starts from entry points: the activities, services and receivers in the merged manifest, views named in XML, and anything a keep rule names. It traces every class, method and field reachable from them (tree shaking), deletes the rest, inlines small methods, merges classes, removes dead branches, and renames what survives. BookNest's release build type turns it on:

app/build.gradle.kts: R8 for release buildsGroovy
buildTypes {
    release {
        isMinifyEnabled = true      // 5.37 R8: shrink, optimize, obfuscate
        isShrinkResources = true    // and remove unused resources
        proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"))
        signingConfig = signingConfigs.getByName("debug")   // for testing; 5.38 signs properly
    }
}

AGP 9.3 added a shorter form, optimization { enable = true }, which also enables optimized resource shrinking and the default rules. BookNest tried it first and went back: with AGP 9.4.0 the baseline-profile plugin could not turn R8 off for its nonMinifiedRelease variant (Baseline Profiles). The results, measured with the SDK's apkanalyzer dex references:

What R8 removed from BookNest
Debug APK Release APK
Size 21,981,979 bytes 4,371,556 bytes
Dex files 18 1
Method references 228,024 33,849

Five of every six bytes were code and resources no screen reaches. R8 records its work in app/build/outputs/mapping/release/: mapping.txt (548,692 lines of old name to new name), usage.txt (what it removed), seeds.txt (what the keep rules held) and configuration.txt (every rule it applied, merged from AGP, libraries and you). Obfuscation shows up at run time too: the release build's Log.d(TAG, "loaded: ${load.value::class.simpleName}") printed loaded: hq2 instead of loaded: Success, so never build logic, analytics names or JSON keys on class names.