What levers do you have to reduce APK/IPA size?
Smaller binaries install more reliably on constrained networks and low-end devices, and the same three moves — shrink code, split by device, defer assets — show up on both Android and iOS under different names.
LEARN BY DOINGPut your intuition to work3 steps
What levers do you have to reduce APK/IPA size?
Use the lesson’s examples and explanation to check your idea.
Personal reflection. Nothing is sent to an AI service.#Why size is a product metric, not just a build number
Install size shows up in conversion funnels well before it shows up in a crash report: on a slow or metered connection, a large download is where a user abandons an install, and both Google Play and the App Store surface size prominently before a user commits to tapping install. Below a certain size threshold, Play used to (and in spirit still does) treat an app as eligible for more markets and better placement in low-bandwidth regions. So "reduce APK/IPA size" isn't a one-time cleanup task — it's an ongoing budget you defend the same way you'd defend a performance budget, because every new dependency, every unconverted PNG, and every unused keep-rule chips away at it quietly.
#Shrinking the code you ship
On Android, R8 (the default shrinker since Android Gradle Plugin 3.4, a drop-in successor to ProGuard that still reads proguard-rules.pro files) does three things in one pass when isMinifyEnabled = true: tree-shaking removes classes and methods nothing reachable calls, minification renames what's left to short names, and a separate optimization pass inlines trivial methods and merges classes where safe. The catch every team hits once: reflection-based code — Gson/Moshi model classes, Parcelable generators, anything found by class name at runtime — looks unreachable to R8's static analysis and gets stripped or renamed unless you add an explicit -keep rule for it, which is why a build that shrinks cleanly in debug can crash in release with a ClassNotFoundException. isShrinkResources = true (which requires minification to be on) does the analogous thing for resources: it drops drawables, layouts, and strings that nothing in the shrunk code or manifest still references, plus alternate-density and alternate-language resource sets you've explicitly excluded via resourceConfigurations.
On iOS, the equivalent lever is dead-code stripping combined with whole-module optimization: the linker removes unreferenced symbols, and building with -Osize (versus -O, which optimizes for speed) tells the compiler to prefer smaller code even at a small runtime cost. Unlike Android's keep-rule problem, Objective-C's dynamic dispatch and Swift's reflection (Mirror) create similar "this looks unused but is called by name" traps, which is why aggressive dead-code stripping is validated against real usage, not just assumed safe.
#Splitting the download by device, not by feature list
The single biggest lever on both platforms is not shipping every device variant to every device. Android's App Bundle (.aab) format replaced the "one universal APK with every ABI, density, and language baked in" model: you upload one bundle, and Google Play's dynamic delivery generates and serves an optimized APK per requesting device — just its ABI, just its screen density, just its languages — so the bytes a user actually downloads are a fraction of what a universal APK would have shipped. iOS's App Thinning does the conceptual equivalent through slicing: an App Store archive is sliced per device architecture and screen class at distribution time, so an iPhone downloads only the slice it needs rather than a fat binary built for every device Apple has ever shipped. Neither of these reduces the size of your source artifact — the .aab or the archive can still be large — but both dramatically reduce what a real user's phone actually downloads, which is the number that matters for conversion.
#Deferring what isn't needed on day one
Beyond splitting by device, both platforms let you split by when content is needed. Android's Play Feature Delivery lets a module be marked on-demand or conditional, downloaded only if a user reaches the feature that needs it (or only on devices meeting a condition, like a minimum API level or supporting a particular hardware feature) rather than bundled into the initial install. iOS's On-Demand Resources (ODR) does the same for assets rather than code: tag a set of images, sounds, or level data as on-demand, and the OS fetches them from Apple's CDN only when the app requests that tag at runtime, purging them again when they're no longer needed — ideal for late-game content or rarely-used feature assets that would otherwise sit in every install whether or not a user ever reaches them.
#Assets are usually the actual weight, not code
For most apps, images dwarf compiled code in raw bytes, so format and delivery choices on assets often move the needle more than any code-shrinking pass. On Android: prefer WebP (both lossy and lossless modes typically beat PNG by 25–35% at comparable quality) or a modern format like AVIF for photographic content, and use VectorDrawable for icons and simple illustrations instead of shipping separate PNGs at mdpi through xxxhdpi — one small XML file scales losslessly to every density. On iOS: prefer HEIC over PNG/JPEG for bundled photographic assets, and use vector PDFs with "Preserve Vector Data" in an asset catalog instead of shipping @1x/@2x/@3x raster copies of every icon. On both platforms, an unused-resource audit is worth running periodically — Android's lint UnusedResources check (with tools:keep/tools:discard for anything found only via Resources.getIdentifier(), which static analysis can't see) and a manual sweep of an Xcode asset catalog for images nothing references anymore — because unused assets accumulate silently as features get removed and nobody remembers to delete their drawables.
| Lever | Android | iOS | What it mainly saves |
|---|---|---|---|
| Code shrinking | R8 (tree-shaking, minification, optimization) via isMinifyEnabled | Dead-code stripping + -Osize whole-module optimization | Compiled code size |
| Per-device delivery | Android App Bundle → Play generates per-device split APKs | App Thinning → App Store slices per architecture/screen | Bytes actually downloaded |
| Deferred/optional content | Play Feature Delivery (on-demand/conditional modules) | On-Demand Resources (ODR) | Initial install size |
| Resource shrinking | isShrinkResources + lint UnusedResources | Manual/asset-catalog unused-image audits | Bundled resource size |
| Image format | WebP/AVIF, VectorDrawable for icons | HEIC, vector PDFs in asset catalogs | Asset size |
| Native/architecture splits | abiFilters / App Bundle ABI splits | Architecture slicing (e.g., arm64-only where supported) | Native binary size |
| Dependency hygiene | Remove duplicate/unused libraries (./gradlew :app:dependencies) | Trim unused frameworks/pods, avoid heavy dynamic frameworks | Code + fixed per-framework overhead |
| Measuring the result | APK Analyzer, bundletool get-size total | Xcode Organizer App Size Report / App Thinning Size Report | Verifies the above actually worked |