| name | binary-size-reduction |
| description | Shrink APK/AAB and IPA size with R8/ProGuard, dead-code elimination, asset hygiene, and Android App Bundle / App Thinning best practices. |
Binary Size Reduction
Instructions
Binary size affects install conversion, update friction, and in emerging markets, whether the app is installed at all. Cut with a combination of code shrinking, asset hygiene, and store delivery features.
1. Budgets
| Platform | Download (p50 device) | On-disk |
|---|
| Android (AAB download split) | ≤ 25 MB | ≤ 80 MB |
| iOS (thinned download) | ≤ 60 MB | ≤ 150 MB |
2. Android — R8 Full Mode
android {
buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}
gradle.properties:
android.enableR8.fullMode=true
Full mode enables aggressive optimizations, horizontal class merging, and method inlining. Expect 10–25% additional shrink on a typical modern app.
3. ProGuard Rules — Only What You Need
Keep rules narrow. Every -keep class com.example.** line prevents shrink on that tree.
Good:
-keepclassmembers class * {
@com.squareup.moshi.FromJson *;
@com.squareup.moshi.ToJson *;
}
Bad (overly broad):
-keep class com.example.** { *; }
Audit with build/outputs/mapping/release/usage.txt — it lists everything R8 kept.
4. Android — App Bundles and Delivery
-
Ship AAB, not APK. Play's Dynamic Delivery slices per ABI, density, and language.
-
Use Dynamic Feature Modules for rarely-used features (onboarding, premium flows).
-
Play Asset Delivery for large assets (ML models, tilesets) — do not bundle in the base module.
-
Remove unused languages: in build.gradle,
android {
defaultConfig {
resConfigs("en", "es", "fr", "de", "ja", "pt", "ru", "zh")
}
}
5. Android — Native Libraries
- Ship only the ABIs you need:
abiFilters = setOf("arm64-v8a", "armeabi-v7a"). Avoid x86/x86_64 unless you target emulators.
- Enable ndk { debugSymbolLevel = "SYMBOL_TABLE" } and upload debug symbols to Play; do not keep symbols in the shipping binary.
- Use 16 KB page sizes (Android 15+) alignment — some NDK libs get smaller when built with
-Wl,-z,max-page-size=16384.
6. iOS — App Thinning and Bitcode Sunset
- Xcode 14 removed Bitcode; do not re-enable it. Waste of time.
- App Thinning: ship from a universal IPA to the App Store; users download a per-device slice.
- Prefer Asset Catalogs with device variants (1x/2x/3x) — the store strips unused slices.
- On-Demand Resources (ODR) for tutorial videos, levels, locale packs.
7. iOS — Dead Code Stripping
Ensure Build Settings:
DEAD_CODE_STRIPPING = YES
STRIP_INSTALLED_PRODUCT = YES
DEPLOYMENT_POSTPROCESSING = YES
GCC_OPTIMIZATION_LEVEL = s // Swift: -Osize
SWIFT_OPTIMIZATION_LEVEL = -Osize
Swift -Osize often saves 5–10% over -O.
8. Asset Hygiene (Both Platforms)
- Replace PNGs with WebP lossless or AVIF; often 30–50% smaller.
- SVG/Vector drawables for icons. Avoid ship of multiple density PNGs for vector art.
- Compress JPEGs:
mozjpeg at q=82.
- Strip EXIF with
exiftool -all=.
- Avoid shipping fonts you do not use. A single font family with 4 weights > 400 KB can bloat quickly.
Gradle task to detect unused resources:
./gradlew lintDebug | Select-String "UnusedResources"
9. Analyze What's Inside
Android:
./gradlew bundleRelease
# open with Android Studio -> Build -> Analyze APK
The APK Analyzer breaks down .dex size per package. Look for:
- Oversized kotlin/swift standard libraries bundled by mistake.
- Duplicate libs from transitive dependencies (e.g.,
okio twice).
- Large JSON/text assets — move to
Play Asset Delivery.
iOS: open the .ipa in the Organizer or unzip and inspect:
unzip -l App.ipa
du -sh Payload/App.app/Frameworks/*
Large .framework entries often come from analytics SDKs; evaluate whether you can swap for smaller alternatives.
Flutter: flutter build appbundle --analyze-size produces a size report JSON plus a DevTools view.
React Native: npx react-native-bundle-visualizer and Hermes .hbc inspection.
10. Dependency Hygiene
- Every dependency costs bytes. Review each one; remove any that solves a 10-line problem.
- Prefer single-purpose libs over kitchen-sink frameworks.
- Use R8/ProGuard tree-shaking by avoiding reflection-heavy libraries in hot paths (Gson → Moshi/kotlinx.serialization).
11. Track Regressions
Fail CI if APK size grows > 2% without an opt-in flag. Store the mapping file and the size-report.json per release.
GitHub Action example:
- name: Fail on APK regression
run: scripts/compare-apk-size.sh artifacts/app-release.aab
Checklist