R8 Configuration Analyzer Turns Keep Rules into Hard Numbers
I believe everyone has recently learned that Google is cracking down on keep rule abuse, recommending that apps enable R8 full mode. Google will score apps, and low scores will trigger warnings for poor performance. Based on this, Google has started turning Keep Rule optimization into a quantifiable engineering process that can be handed off to an Agent.
In reality, Android projects doing R8 optimization have always had a frustrating problem: minifyEnabled = true is just the beginning. As the project grows, the app's own proguard-rules.pro, along with consumer rules carried by internal modules and third-party AARs, eventually form a very complex set of constraints, such as:
- Can R8 delete a certain class?
- Can it inline a certain method?
- Can it modify class structure and names?
Previously, checking these issues mostly relied on manual inspection. For example, seeing
-keep class com.foo.** { *; }, a developer might wonder if its scope is too broad. But without understanding the project, it's hard to judge whether it affects 20 classes or 2000 classes, which methods cannot be inlined, which fields cannot be shrunk, and whether this rule is needed by the app itself or brought in by a dependency via consumer rules.
So this time, Google has added the R8 Configuration Analyzer. It directly uses R8's understanding of the entire app and configuration to quantify the impact of keep rules on the final app. At the same time, Google has provided the r8-analyzer Skill in the official android/skills repository, organizing the running, data conversion, result analysis, and report generation of the Configuration Analyzer into a process that can be handed off to a Coding Agent for execution.
So what exactly does the Configuration Analyzer analyze? R8's keep rules essentially add constraints to the optimizer, for example:
-keep class com.example.feature.** { *; }
Textually, this rule preserves classes and members under a certain package. But for performance optimization, the question is:
In the current Release Build, exactly how many classes, fields, and methods are matched by it, and which optimization capabilities have these objects lost?
The Configuration Analyzer's role is to do this after R8 has already obtained the complete program graph. The official results are summarized into three key metrics:
- Shrinking Score
- Optimization Score
- Obfuscation Score
These scores express "what proportion of the current code still allows R8 to perform the corresponding operation." For example:
An Optimization Score of 66% means that currently about 66% of classes, fields, and methods can still accept R8's optimization, while the remaining ~34% cannot participate in corresponding optimizations due to configuration constraints. R8's optimization here includes method inlining, class merging, and other processes that directly affect the app's runtime structure.
The Shrinking Score measures the scope where R8 can perform unused code elimination.
The Obfuscation Score reflects how many classes, fields, and methods are still allowed to be renamed.
These three scores turn the previously vague ProGuard programming into quantifiable data. For example, after a dependency upgrade:
Optimization Score 82% → 61%
Shrinking Score 91% → 73%
Obfuscation Score 95% → 94%
This allows you to directly judge that the problem mainly lies in shrinking and optimization, not obfuscation. Next, you can trace the most impactful keep rules from the Analyzer, which is much more efficient than searching for -keep across the entire project.
More importantly, the Configuration Analyzer analyzes the final merged configuration applied to the application, meaning consumer keep rules provided by third-party libraries also enter this analysis process.
Google specifically mentions that library authors usually don't know the specific usage patterns of the host app, so consumer rules are sometimes written conservatively, even restricting application code outside the library. The Analyzer can show the source of these rules and analyze the impact of all merged consumer rules on the application.
Yes, that's talking about me. My SDK rules are indeed very broad, not because I want to be lazy, but for the sake of allowing developers more flexibility.
This is actually quite important for large projects, because performance issues generally might not be in app/proguard-rules.pro. You might check your own project files and find them very clean, but the rule truly affecting thousands of methods is hidden inside some AAR.
Another concept is Blast Radius. This is an internal data structure concept within Google's r8-analyzer. The data generated by the Analyzer includes a keep_rule_blast_radius_table. Under each rule, it further records class_blast_radius, field_blast_radius, and method_blast_radius, along with kept_by, keep constraint, file source, and Maven coordinates.
In other words, R8 doesn't just tell you "this rule is broad"; it actually establishes the relationship of "which rule restricts which app elements." For example, a rule like -keep class com.example.** { *; } can ultimately be converted into more engineering-valuable information:
This rule affects:
Classes: 214
Fields: 863
Methods: 1732
Corresponding constraints:
DONT_OPTIMIZE
DONT_SHRINK
DONT_OBFUSCATE
Source:
A specific proguard file / library
The analysis script included with the r8-analyzer Skill directly reads this data. It iterates through kept_class_info_table, kept_field_info_table, and kept_method_info_table, then links each object's kept_by to the keep rule, counting the number of restricted objects based on DONT_OPTIMIZE, DONT_OBFUSCATE, and DONT_SHRINK.
The total number of live classes, live fields, and live methods in the current build is used as the denominator to calculate the three scores.
Then, according to the rule base, R8 tells you "package wildcards have higher risk." The Configuration Analyzer can know exactly what this specific rule hits in the current real build and how large the actual constraint scope it creates is.
Google's Skill also retains a set of heuristic risk-level rules, used only when quantitative data is unavailable. For example, package-wide wildcards are placed at the highest priority, and ! inversion and whole-class member wildcards are also listed as high-impact patterns.
Simply put, Google itself distinguishes two types of analysis:
- When compiler data is available, look at the real Blast Radius.
- For older projects that cannot generate this data, fall back to syntax and experience-based checks.
Then, the Configuration Analyzer also specifically analyzes subsumed rules. For example, if a project simultaneously has:
-keep class com.example.package.** { *; }
-keep class com.example.package.User
Here, the second rule itself is not wrong from the config file perspective, and might even be very precise. However, since the first rule already covers the entire package, the effect of the second rule is actually completely contained within the first.
This situation is very common in long-history Android projects. Different teams, different libraries, and different eras add rules separately, eventually resulting in massive overlaps in the configuration.
The Configuration Analyzer can also explicitly establish this subsumption relationship, simultaneously showing which rule covers another.
Google's official recommended approach is also to first identify classes, fields, and methods truly accessed through dynamic mechanisms like reflection, then compare the impact scope of broad rules versus narrow rules.
If the narrow rule already fully describes the real requirement, the overly broad rule can be addressed. After making modifications, regenerate the Analyzer report and test with a Release Build.
An important boundary here: The Configuration Analyzer can tell you what a rule restricts, but it cannot prove for you that a certain object definitely does not need to be kept at runtime.
When R8 faces situations like Class.forName(), getDeclaredField(), JNI, serialization frameworks, or dynamic scanning based on annotations, it cannot rely solely on conventional static call graphs to deduce all relationships. So, the Analyzer's data solves the question of "how large is the impact scope." When truly deciding whether a rule can be deleted, you must still return to the program semantics.
Furthermore, starting from AGP 9.3, the Configuration Analyzer has become an independent development loop. AGP 9.3+ provides a standalone Gradle Task:
./gradlew :app:analyzeReleaseR8Config
The new standalone task can analyze configuration changes without needing to fully generate an APK or App Bundle. It is officially recommended as the way to repeatedly debug keep rules locally. The HTML report is output by default to:
app/build/reports/r8/r8-config-analyzer-release.html
When fully executing R8 Release Builds like assembleRelease, the Analyzer report is also automatically generated, defaulting to:
build/outputs/mapping/release/configanalyzer.html
As for the r8-analyzer Skill, it essentially writes the entire process into an SOP. The current version first checks build.gradle, build.gradle.kts, gradle.properties, and libs.versions.toml to determine the AGP/R8 version, then selects one of three execution paths.
- For AGP 9.3 and above, directly run
./gradlew :app:analyzeReleaseR8Config, then read the generated protobuf, convert it to JSON using the Skill's built-in script, and run the analysis script to generateanalysis_result.txt. - If AGP is below 9.3 but R8 has reached 9.3.7-dev, use R8's
dumpkeepradiustodirectoryto output the raw Blast Radius protobuf, then similarly enter the JSON and quantitative analysis process. Google has even prepared the protobuf schema, conversion script, and analysis code in the reference files. - Only when the project doesn't even have an R8 version supporting the Configuration Analyzer does the Skill enter the heuristic path, manually checking
proguard-rules.proand making judgments based on Google's provided keep-rule impact hierarchy and reflection guide.
So in summary, the R8 Configuration Analyzer solves "which Keep Rule restricts how much real code," and the r8-analyzer Skill solves the problem of letting a Coding Agent turn this compiler data into actionable engineering conclusions following a reliable process.
This feature is actually really excellent, almost free, because you can just let AI handle it directly.
Top 1 of 2 from juejin.cn, machine-translated. The original thread is authoritative.
Is no one reading technical articles anymore?
Yeah, I guess so [grin]