R8, Shrinking & App Size Quiz
BUILD & TOOLING › Gradle
Which statement about shrinkResources is correct?
- It removes unused code as well as unused resources.
- It only takes effect when minifyEnabled (R8) is also enabled.
- It automatically detects resources referenced only via reflection.
- It works by compressing resources.arsc to save space.
Answer: It only takes effect when minifyEnabled (R8) is also enabled.
Resource shrinking relies on R8's code reference graph, so shrinkResources is a no-op unless minifyEnabled is true; reflective references must be declared manually (e.g., keep.xml).
Which library most commonly requires you to hand-write keep rules for your model classes because it doesn't ship adequate consumer rules?
- Hilt
- kotlinx.serialization
- Gson
- Moshi (codegen)
Answer: Gson
Gson resolves fields purely by runtime reflection and ships no rules for your models, so R8 renames them and breaks parsing. Hilt, kotlinx.serialization, and Moshi codegen bundle their own consumer ProGuard rules.
What is the purpose of R8's mapping.txt?
- Map obfuscated names back to originals to read release stack traces.
- List every single resource removed during the resource shrinking pass.
- Declare which classes and members R8 must keep from obfuscation.
- Record DEX method counts to track against the 64K reference limit.
Answer: Map obfuscated names back to originals to read release stack traces.
mapping.txt records the original-to-obfuscated name mapping for a build; you retrace or upload it to the Play Console to read crashes. Keep declarations live in proguard-rules.pro, not mapping.txt.
Compared to compatibility mode, what should you expect from R8 full mode (the modern default)?
- Name obfuscation is completely disabled by default once full mode is active.
- Resource shrinking is automatically switched off whenever full mode runs.
- It never requires any additional keep rules for reflective code.
- Tougher optimization, stricter reflection assumptions, may need keep rules.
Answer: Tougher optimization, stricter reflection assumptions, may need keep rules.
Full mode makes stronger assumptions (it assumes no unexpected reflection), enabling more aggressive shrinking and optimization, which can demand additional keep rules for reflective code.
An Android App Bundle lets Google Play generate optimized split APKs primarily along which dimensions?
- API level, OEM, and carrier-specific device config
- Screen density, ABI/architecture, and language
- Battery level, available RAM, and storage capacity
- Theme, orientation, and font scale settings
Answer: Screen density, ABI/architecture, and language
Play splits the bundle by screen density, ABI, and language so each device downloads only the matching configuration, which is the core size benefit of the AAB format.
What distinguishes -keep from -keepclassmembers in an R8 rules file?
- -keep keeps the class and its members; -keepclassmembers keeps only members, class removable.
- -keep targets app resources whereas -keepclassmembers targets code members.
- -keep and -keepclassmembers are treated as exact aliases with identical behavior.
- -keepclassmembers fully disables name obfuscation across the entire app build.
Answer: -keep keeps the class and its members; -keepclassmembers keeps only members, class removable.
-keep is the broad rule that retains the class plus members; -keepclassmembers is narrower, retaining only the specified members and allowing the class itself to be shrunk away if nothing references it.
Why should resources.arsc be left uncompressed inside the APK/bundle?
- Because Android lint refuses to run at all when this file is compressed.
- To keep the generated mapping.txt retrace file accurate and valid.
- Because R8 must be able to obfuscate the names stored inside it.
- ART memory-maps directly into it, so compressing it hurts performance.
Answer: ART memory-maps directly into it, so compressing it hurts performance.
ART and the framework read resources.arsc by seeking/mmapping into the file directly; compressing it forces full decompression and degrades runtime performance, so it is kept stored, not deflated.
What does the androidx.annotation @Keep annotation do when R8 is enabled?
- It marks the annotated class for inclusion in a dynamic feature module.
- It tells R8 to keep the annotated class or member from shrink or rename.
- It forces resource shrinking to retain any drawable referenced by that class.
- It is only a Kotlin compiler hint and has no effect on the R8 shrink pass.
Answer: It tells R8 to keep the annotated class or member from shrink or rename.
@Keep is honoured by the default optimize rules, which keep @Keep-annotated classes and members from being tree-shaken or obfuscated, making it a code-local alternative to a -keep rule.
Which Play Feature Delivery mode downloads a dynamic feature module only when the running app explicitly requests it via SplitInstallManager?
- on-demand delivery
- install-time delivery
- conditional delivery
- instant delivery
Answer: on-demand delivery
On-demand delivery fetches the module at runtime when the app calls SplitInstallManager, keeping it out of the initial install. Install-time and conditional modes decide inclusion at install time, and instant delivery is not a feature-module mode.
When deobfuscating a production stack trace with retrace, why must you use the mapping.txt generated for that exact build?
- Because mapping.txt also stores the app's signing key, which rotates each build.
- R8 renames members differently each build, so a mismatched mapping is wrong.
- Because retrace recompiles the app from the mapping file to reproduce the crash.
- Because the Play Console deletes the previous mapping the moment a new one is uploaded.
Answer: R8 renames members differently each build, so a mismatched mapping is wrong.
Each R8 run renames members independently, so a mapping from a different build maps the obfuscated names to the wrong originals; only the matching build's mapping.txt yields a correct retrace.
Why might you replace a Kotlin enum with an @IntDef in size-sensitive code?
- R8 cannot handle enums at all, so replacing them avoids a build crash.
- @IntDef values are packaged in assets, so they never count toward the DEX.
- Each enum adds about 1.0–1.4 KB to the DEX; @IntDef stays as ints with checks.
- @IntDef turns on resource shrinking for those values, cutting code size automatically.
Answer: Each enum adds about 1.0–1.4 KB to the DEX; @IntDef stays as ints with checks.
An enum generates a full class plus value array that costs about a kilobyte or more in classes.dex; @IntDef provides the same type safety at compile time but reduces to ordinary int constants R8 keeps tiny.
What distinguishes proguard-android-optimize.txt from the plain proguard-android.txt default config?
- It disables obfuscation so stack traces stay readable during release builds.
- It turns resource shrinking off to make the build pipeline run faster.
- They are the same file; the optimize one just exists for older projects.
- It also enables R8 optimizations like inlining and class merging.
Answer: It also enables R8 optimizations like inlining and class merging.
The plain default historically suppresses optimizations; proguard-android-optimize.txt removes that restriction so R8 can inline, merge classes, and rewrite dead branches in addition to shrinking and obfuscating.
In the modern Android build toolchain, which earlier tool did R8 replace, and what additional job does R8 fold into the same pipeline?
- It replaced ProGuard for shrinking and obfuscation, and it also dexes.
- It replaced Lint and now handles static analysis during release builds.
- It replaced AAPT2 and now compiles and links app resources too.
- It replaced bundletool and now creates per-device split APKs.
Answer: It replaced ProGuard for shrinking and obfuscation, and it also dexes.
R8 is the default replacement for ProGuard, and because it is built on top of D8 it shrinks, optimizes, obfuscates, and dexes in one pass rather than running a separate dexing step.
What is the effect of the modern default android.packaging.jniLibs.useLegacyPackaging = false for native libraries?
- It compresses the .so files to save space, which can slow startup.
- It strips debug symbols from the .so files automatically during packaging.
- It stores the .so files uncompressed and page-aligned for direct loading.
- It moves the native libraries into an on-demand dynamic feature module.
Answer: It stores the .so files uncompressed and page-aligned for direct loading.
With legacy packaging off, native libraries are stored uncompressed and page-aligned inside the APK, so they are loaded directly from the package instead of being decompressed and extracted to disk at install time.