Reducing Mobile App Binary Size Without Losing Features

Binary size matters more than most teams realize. Google's own research shows that for every 6 MB increase in APK size, install conversion rates drop by 1%. Apple imposes a 200 MB limit for cellular downloads. In emerging markets where storage space is limited and data is expensive, a bloated app gets uninstalled first when the user's phone runs out of space.

The good news is that most apps carry significant unnecessary weight. Unused resources, unoptimized images, debug symbols shipped in release builds, and bloated third-party SDKs all contribute. Here is a systematic approach to reducing binary size on both Android and iOS without removing any user-facing functionality.

Measure Before You Cut

Before making any changes, establish a baseline and understand where the size is coming from. Cutting blindly leads to broken features and wasted effort on areas that do not matter.

Android: APK Analyzer and bundletool

Android Studio's APK Analyzer (Build > Analyze APK) breaks down your APK into its component parts: DEX files, resources, native libraries, and assets. Sort by size to see exactly what is consuming space. The classes.dex file contains your compiled code and all dependencies. The res/ folder contains your layouts, drawables, and other XML resources. The lib/ folder contains native .so libraries, which are often the largest single contributor.

For more granular analysis, use bundletool build-apks to generate device-specific APKs from your App Bundle and measure what an actual user downloads. The universal APK size is misleading because no single user downloads the universal build.

iOS: Xcode App Size Report

In Xcode, archive your app and select "Distribute App" with the "App Thinning" option. Choose "All compatible device variants" to generate a size report. This report shows the compressed and uncompressed size for each device type. The App Thinning Size Report breaks down the binary into the executable, frameworks, asset catalogs, and other resources.

You can also use xcrun altool --validate-app or examine the .ipa file directly by renaming it to .zip and inspecting its contents.

Android App Bundles vs. APK

If your Android app still ships as a monolithic APK, switching to the Android App Bundle (AAB) format is the single highest-impact change you can make. Google Play generates optimized APKs for each device configuration, stripping out resources for screen densities, CPU architectures, and languages that the specific device does not need.

A typical app sees a 20% to 40% reduction in download size from this switch alone. The migration is straightforward: change your build output from APK to AAB in your build.gradle and upload the .aab file to Play Console instead of the .apk. Google Play handles the rest.

One caveat: if you distribute outside the Play Store (direct APK downloads, alternative stores), you will still need to generate APKs. Use bundletool build-apks --mode=universal for a single APK, or generate configuration-specific APKs for distribution.

iOS App Thinning

Apple's App Thinning consists of three technologies: slicing, bitcode (now deprecated as of Xcode 14), and on-demand resources. Slicing is automatic: the App Store generates device-specific variants that exclude assets for other screen scales and architectures. An iPhone user does not download iPad-only assets, and a device running on arm64 does not receive armv7 code.

On-demand resources (ODR) let you host assets on the App Store and download them only when needed. This is useful for apps with large asset catalogs, such as games with level-specific textures or education apps with downloadable content packs. Tag resources in your asset catalog with on-demand resource tags, and use the NSBundleResourceRequest API to fetch them at runtime.

Image and Asset Optimization

Images are often the largest category of assets in a mobile app. Optimizing them provides substantial returns.

Format Selection

Resolution and Compression

Audit your image assets for unnecessarily large dimensions. A background image at 4000x3000 pixels that gets displayed at 1080x1920 is carrying three times more data than needed. Resize images to match their maximum display size, accounting for screen density.

For photographs and complex images, tune compression quality. JPEG quality of 80% is visually indistinguishable from 100% for most content but produces significantly smaller files. Tools like pngquant (lossy PNG compression) and mozjpeg can reduce existing assets without visible quality loss.

Code Shrinking with ProGuard and R8

On Android, R8 (the successor to ProGuard) removes unused code, renames classes and methods to shorter names, and optimizes bytecode. Enable it in your release build type:

R8 typically reduces DEX file size by 10% to 30%. The combination of code shrinking and resource shrinking can have a dramatic effect, especially in apps with large dependency trees where many library classes go unused.

On iOS, the compiler performs dead code stripping automatically when Link-Time Optimization (LTO) is enabled. Check your build settings to ensure "Dead Code Stripping" is set to Yes and "Link-Time Optimization" is set to "Incremental" for release builds.

Removing Unused Resources

Over time, apps accumulate resources that are no longer referenced: old icons from a previous redesign, strings for removed features, layouts for screens that no longer exist. These dead resources add weight without value.

On Android, shrinkResources true handles this automatically in release builds. For a manual audit, Android Studio's "Remove Unused Resources" refactoring (Refactor > Remove Unused Resources) identifies unreferenced resources. Run it, review the results carefully (some resources are loaded dynamically by name and will be missed by static analysis), and delete what is truly unused.

On iOS, there is no built-in equivalent. Tools like FengNiao or LSUnusedResources scan your project for image assets that are not referenced in code or storyboards. Manual review is still important because some assets are referenced dynamically using string interpolation.

Dynamic Feature Modules (Android)

For larger apps, Android's dynamic feature modules let you split functionality into modules that are downloaded on demand rather than included in the initial install. Users who never use a specific feature never download the code and assets for it.

Good candidates for dynamic feature modules include: onboarding flows that run once, advanced editing tools used by a subset of users, region-specific features, and diagnostic or debug tools. The Play Core library provides the SplitInstallManager API for requesting and managing module downloads at runtime.

The tradeoff is added complexity in your build configuration and runtime code. Each dynamic module needs its own Gradle module, manifest, and resource set. You also need to handle the case where a user tries to access a feature whose module has not yet been downloaded.

Auditing Third-Party SDKs

Third-party SDKs are frequently the largest contributors to binary size, and teams often do not realize how much weight they add. A single analytics SDK can add 2 to 5 MB. An ad mediation SDK with multiple ad network adapters can add 10 MB or more.

Review every SDK in your dependency list and check its size contribution using APK Analyzer or the Xcode size report. Ask whether each SDK is still needed, whether a lighter alternative exists, and whether you are using enough of its functionality to justify the size cost. Sometimes replacing a full SDK with a few direct API calls removes megabytes from the binary.

Binary size is not a one-time fix. It is a maintenance discipline. Set a size budget, track it in CI, and treat size regressions the same way you treat performance regressions: investigate, understand, and address them before they accumulate.

App size optimization is one of the ongoing maintenance tasks that DEVSFLOW handles for the apps we manage. Learn about our maintenance plans and how we keep production apps lean and fast.