
Most Android developers have a love-hate relationship with R8. We love the smaller APKs and better runtime performance it delivers, but we often curse it when a seemingly innocuous release build crashes with an obscure error, or our APK size balloons unexpectedly.
When things go wrong, the first reflex is to check the stack trace, maybe adjust a simple -keep rule, and hope for the best. But what if I told you there's a treasure trove of debugging information generated by R8 itself, sitting right in your build folder, that can save you hours of guesswork?
This article dives deep into the R8-generated metadata files β specifically the whyareyoukeeping output and the R8 Configuration Fileβto give you the power to truly understand the rule set applied to your final APK and diagnose build size issues like a pro.
1. The R8 Configuration File: The Complete Rulebook
When R8 runs, it doesn't just look at your project's proguard-rules.pro file. It aggregates rules from every source:
- Your project's
proguard-rules.profile. - Rules embedded within AARs from your dependencies (e.g., KTX libraries, Firebase, etc.).
- Rules automatically generated by the Android Gradle Plugin (AGP) for things like Activity, View, and Parcelable implementations.
This is where the R8 Configuration File comes in. This file is a complete, consolidated record of every single rule R8 used during its optimization and minification phase.
π‘ How to Find It (and What to Look For)
The complete R8 configuration file is typically found in:
app/build/intermediates/proguard-files/full-r8-config.txt
If you open this file, you'll see hundreds, perhaps thousands, of rules. Look closely, and you'll find comments indicating the source of the rules.
# From: /path/to/.gradle/caches/transforms-3/..../jetified-lifecycle-viewmodel-ktx-2.5.1/proguard.txt
-keepclassmembers class * extends androidx.lifecycle.ViewModel {
<init>(...);
}
# From: /path/to/app/build/intermediates/merged_rs_config_files/release/out/r8-config.txt
# AGP-generated rule for keeping com.myapp.data.models.AppUser
-keep class com.myapp.data.models.AppUser {
<fields>;
<methods>;
}
Your Takeaway: If you're debugging a weird obfuscation issue, or a class being unexpectedly kept, check this file first. You might discover a hidden rule from a dependency that is overriding your local rules.
2. The whyareyoukeeping Output: Diagnosing Bloat and Keep-Rule Sprawl
One of the most frustrating build issues is unexpected APK size increase. You check your code, you remove a dependency, but the size remains stubbornly high. This is usually due to a chain reaction: a single -keep rule causes R8 to keep one class, which forces it to keep its dependencies, and so on.
The whyareyoukeeping metadata file is R8's self-reporting mechanism. It tells you exactly which rule caused a specific piece of code to be kept in the final APK.
π‘ How to Generate and Use It
You need to explicitly ask R8 to generate this report by adding a configuration rule to your proguard-rules.pro file:
# 1. Enable the whyareyoukeeping output.
# This tells R8 to generate the keeping report.
-printusage C:\path\to\app\build\outputs\r8_metadata\release\usage.txt
# 2. Tell R8 which specific classes/members you are curious about.
# You MUST add a custom keep rule for the artifact you want to track.
# Here, we track the 'LegacyLogger' class.
-whyareyoukeeping class com.myapp.util.LegacyLogger
After a full build, you will find the generated report file at the path you specified in -printusage.
Let's look at an enhanced example using a hypothetical Kotlin data class:
// com.myapp.analytics.models.EventPayload.kt
// Comment: This data class is ONLY used by a separate analytics library
// that we want R8 to fully remove if we don't call it.
// However, the APK size is too large!
data class EventPayload(
val eventName: String,
val timestamp: Long,
val metadata: Map<String, String>? = null
)
Now, let's look at the (hypothetical) output in usage.txt:
com.myapp.analytics.models.EventPayload {
* is referenced in keep rule:
/path/to/app/build/intermediates/proguard-files/full-r8-config.txt:75:5: -keep class com.myapp.analytics.** { *; }
com.myapp.analytics.models.EventPayload android.os.Parcelable.Creator CREATOR:
is referenced in keep rule:
/path/to/.gradle/caches/transforms-3/.../jetified-parcelize-runtime-1.6.0/proguard.txt:1:1: -keepnames class * implements android.os.Parcelable
com.myapp.analytics.models.EventPayload java.lang.String getEventName():
is kept by the consumer rule:
-keep class com.myapp.analytics.models.EventPayload { *; }
}
π οΈ Diagnosis & Action
-
Look at the first entry: The whole
EventPayloadclass is being kept because of a very broad rule:-keep class com.myapp.analytics.** { *; }.-
Action: This is an overly aggressive keep rule, likely from an old or poorly maintained analytics dependency. You need to identify the source of this rule in
full-r8-config.txtand try to override or eliminate the dependency causing it.
-
Action: This is an overly aggressive keep rule, likely from an old or poorly maintained analytics dependency. You need to identify the source of this rule in
-
Look at the second entry (Parcelable): The
CREATORfield is being kept because of an AGP/library rule related to Parcelable.-
Action: This is expected if
EventPayloadimplements Parcelable. This is a necessary keep, so no action is required here.
-
Action: This is expected if
Your Takeaway: The whyareyoukeeping output is the definitive source for build bloat diagnosis. Don't guess; let R8 tell you which rule is the culprit.
3. Dynamic Rule Application (Advanced)
Sometimes, you only want to keep certain classes when a specific build configuration is active, like when a feature flag is enabled. While you can use buildTypes or productFlavors, you can also leverage R8's ability to read different config files.
Here is a simplified Kotlin/Gradle example to dynamically apply a keep-rule based on a simple boolean:
// In your app/build.gradle.kts (Kotlin DSL)
android {
// ...
buildTypes {
release {
val enableLegacyFeature = project.properties["enableLegacyFeature"] as String? ?: "false"
// Define the path to the extra rule file
val legacyKeepRulesPath = "${project.rootDir}/config/r8/legacy_feature_rules.pro"
// Conditionally add the extra rule file
if (enableLegacyFeature.toBoolean()) {
// Comment: Only apply this file if the system property is true!
// This keeps LegacyFeature classes ONLY in this scenario.
proguardFiles(legacyKeepRulesPath)
}
// Always apply the baseline rules
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}
Now, you can build with:
# This build will include the LegacyFeature classes
./gradlew assembleRelease -PenableLegacyFeature=true
# This build will NOT include the LegacyFeature classes
./gradlew assembleRelease
This dynamic approach keeps your main proguard-rules.pro file clean and ensures that the legacy code is only included when absolutely necessary, further optimizing your APK size for the majority of users.
Frequently Asked Questions (FAQs)
Q: I'm seeing a full-r8-config.txt file and also a r8-config.txt file in my build. What's the difference?
A: The r8-config.txt files you see in intermediate folders (like merged resources) are partial configuration files generated by specific build tasks or AARs. The full-r8-config.txt is the final, grand consolidated list of all rules that R8 actually used for the final APK processing. Always refer to the full-r8-config.txt for the ultimate truth.
Q: Does adding the -whyareyoukeeping rule permanently increase my build time or APK size?
A: No. The -whyareyoukeeping and -printusage rules are informational flags. They instruct R8 to generate a textual report, which takes a negligible amount of time during the final build phase. They do not affect the final optimized code or APK size. However, it's best practice to remove them from your final, checked-in proguard-rules.pro after you have finished debugging.
Q: How do I remove a rule that is coming from a library's AAR?
A: You can't directly remove rules embedded in AARs. However, you can often mitigate overly broad library rules by writing a more specific and contradictory rule in your own proguard-rules.pro. For instance, if a library keeps a whole package, you can try to explicitly # ASSUMEKEPT the class you do want to keep, and then use optimization flags to strip the rest, though this can be tricky. A better approach is often to look for an updated version of the library that uses more precise rules.
Final Thoughts and Next Steps
The R8 metadata files are not just a debug log; they are the master plan R8 executed. By stepping beyond the simple stack trace and leveraging the full-r8-config.txt and the whyareyoukeeping output, you gain an unprecedented level of control and insight into your final application size and runtime behavior.
Stop guessing why your code is being kept. Start asking R8 to tell you.
Questions for the Reader
- Have you ever encountered a mysterious bug that was ultimately traced back to an auto-generated AGP or library R8 rule? Share your experience in the comments!
- What is the most unusual or surprising class you've found kept in your final APK, and what rule (according to
whyareyoukeeping) was responsible?
Top comments (0)