Resource shrinking has been around forever, but it's always been... okay. You enable it, you get some size reduction, but there's always unused stuff that slips through.
Android Gradle Plugin 8.12 changes this. Google integrated resource shrinking directly into R8's optimization pipeline, and the results are noticeably better.
Why the Old Approach Leaked Resources
Traditional resource shrinking worked as a separate step from code optimization. AAPT2 would generate keep rules, R8 would do its thing, and then resource shrinking would run. The problem: those AAPT2 keep rules were unconditional.
This meant:
- Code referencing unused resources stayed in the APK
- Resources only used by dead code stayed too
- Neither optimizer could see the full picture
The new approach runs both in the same R8 pipeline. Resources and code get analyzed together, so if code is dead, its resources go away too. And vice versa.
Enabling It
Add this to gradle.properties:
android.r8.optimizedResourceShrinking=true
You also need both minification and resource shrinking enabled in your release build:
android {
buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}
That's it. Build your release APK and compare sizes.
What Kind of Reduction to Expect
Google claims 50%+ for apps with shared resources. In practice, it depends heavily on your app structure.
If you have a lot of library resources you don't use, or feature modules with resources only used in specific configurations, you'll see bigger gains. If your app is already lean and every resource is directly referenced, the improvement will be smaller.
Use APK Analyzer (Build → Analyze APK in Android Studio) to see where your size is actually coming from. Compare before and after - the res/ folder size should drop noticeably.
Watch Out For Dynamic Resource Loading
The biggest gotcha: resources loaded by string name at runtime.
// This will break - the optimizer can't see this reference
val resId = resources.getIdentifier("icon_$category", "drawable", packageName)
The optimizer doesn't know you're using those resources, so it removes them. Your app crashes with Resources$NotFoundException.
Fix it either by using direct references:
val resId = when(category) {
"food" -> R.drawable.icon_food
"travel" -> R.drawable.icon_travel
else -> R.drawable.icon_default
}
Or by adding a keep file at res/raw/keep.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@drawable/icon_*" />
ProGuard Rules for Resources
If you need to keep specific resources that are referenced dynamically:
# Keep resources referenced by name at runtime
-keep class **.R$drawable {
public static final int splash_logo;
public static final int app_icon_*;
}
Testing Your Build
After enabling optimized shrinking:
- Build a release APK
- Install and test the app thoroughly
- Pay special attention to features that load resources dynamically
- Test on different device configurations (screen densities, languages)
If something breaks, check your dynamic resource loading first. Nine times out of ten, that's the issue.
When It Becomes Default
Optimized resource shrinking is opt-in now, but it'll become the default in AGP 9.0.0. If you enable it now, you get ahead of any issues before you're forced to deal with them.
Is It Worth It?
For most apps, yes. The size reduction is real, and smaller APKs mean faster downloads and less storage pressure on user devices. The main cost is testing and potentially updating any dynamic resource loading patterns.
If you're distributing APKs directly (not through Play's AAB splitting), the savings are even more valuable since you're not getting per-device optimization anyway.
Full technical details in Google's announcement.