R8 Full Mode

Full Mode and Debugging a Broken Release Build

Since AGP 8.0, R8 234 runs in full mode by default: it no longer keeps a class's default constructor or its generic signatures and annotations unless a rule asks, and it merges and inlines more freely than the old ProGuard-compatible mode. Code that uses reflection (Class.forName, Gson 24,235 's TypeToken, a Retrofit 43,944 interface without its library rules) is what breaks, usually as ClassNotFoundException, NoSuchMethodException or a null field in the release build only. android.enableR8.fullMode=false in gradle.properties is a temporary escape hatch, not a fix.

Debug such a build with three tools. First, ask R8. The -whyareyoukeeping rule of ProGuard Rules and Keep Lists printed its reasoning during assembleRelease:

Output of 204
> Task :app:minifyReleaseWithR8
com.example.booknest.data.Book
|- is instantiated in:
|  java.util.ArrayList com.example.booknest.data.BookKt.parseBooks(java.lang.String)
|- is invoked from:
|  void com.example.booknest.LegacyActivity.onCreate(android.os.Bundle)
...
|- is referenced from xml:
|  AndroidManifest.xml

Second, read the crash with the mapping file. BookNest's --ez testCrash true switch throws on purpose from Diagnostics.testCrash(), the way a crash reporter's test button does. In the release build it logged:

Output of 204
java.lang.IllegalStateException: test crash after loading 6 books
    at vb1.p(r8-map-id-3d04e49be2b8de0577d73f350033d9f91003f83f7f5164c9f0479d3c7cc60655:3)
    at cs3.c(r8-map-id-3d04e49be2b8de0577d73f350033d9f91003f83f7f5164c9f0479d3c7cc60655:74)

The r8-map-id names the exact mapping that build wrote. The SDK's retrace tool (in cmdline-tools/latest/bin, and built into Play Console 1 and Android Studio 234 ) turns it back:

Retracing a release stack traceKotlin
retrace app/build/outputs/mapping/release/mapping.txt crash.txt
Output
java.lang.IllegalStateException: test crash after loading 6 books
        at com.example.booknest.data.Diagnostics.testCrash(Diagnostics.java:7)
        at com.example.booknest.data.Diagnostics$testCrash$1.invokeSuspend(Diagnostics.kt)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:3
          4)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)

Keep every release's mapping.txt: without the matching file a stack trace stays unreadable, and Play Console needs the same file to deobfuscate crash reports (an app bundle carries it, Publishing to Google Play). Third, run tests against the release build (testBuildType = "release") before users do.