On Android, why does the system tell me that my app is much larger than of the uncompressed APK?

Summary

An application developer observed a massive discrepancy between their uncompressed APK size (13.4MB) and the final installed size reported by the Android system (24.6MB). Even accounting for the original APK being stored on the device, the math does not add up. This postmortem explores why the Android OS reports a size significantly larger than the sum of the compressed and uncompressed assets.

Root Cause

The discrepancy is driven by three distinct layers of storage management and OS overhead:

  • Uncompressed Asset Expansion: When an APK is installed, the system extracts compressed resources (images, sounds, etc.) into their raw, uncompressed form.
  • ART (Android Runtime) Optimization: This is the primary culprit. During installation or when the device is idle, the system runs AOT (Ahead-Of-Time) compilation. It converts DEX (Dalvik Executable) files into machine code (OAT files) to improve execution speed. This machine code is significantly larger than the original bytecode.
  • System Metadata and Padding: The Android package manager maintains various databases, internal caches, and file system overheads that contribute to the total footprint reported in “App Info.”

Why This Happens in Real Systems

In high-scale production environments, this “size bloat” is a trade-off between storage efficiency and runtime performance.

  • Performance over Space: Android prioritizes reducing CPU cycles during app startup and method execution. By pre-compiling code into native machine instructions, the system avoids the heavy lifting of Just-In-Time (JIT) compilation every time the app runs.
  • Filesystem Alignment: Modern filesystems (like ext4 or F2FS) use block sizes (typically 4KB). Small files or specific data structures are often padded to align with these blocks, causing a discrepancy between “actual data size” and “space used on disk.”

Real-World Impact

  • User Churn: On low-end devices with limited storage, a “small” 10MB app that expands to 50MB after installation can lead to users uninstalling the app or refusing to update.
  • Bandwidth Costs: Developers often optimize for Download Size (to save CDN costs), but if the Install Size is massive, the perceived value of the app drops for the end user.
  • Update Friction: Large installed footprints can trigger “Insufficient Storage” errors during Play Store updates, even if the new APK is small.

Example or Code

The following logic demonstrates how the system calculates the different layers of size:

def calculate_android_footprint(apk_compressed, apk_uncompressed, dex_optimization_multiplier):
    # Layer 1: The original file downloaded
    download_size = apk_compressed

    # Layer 2: The extracted contents
    extracted_size = apk_uncompressed

    # Layer 3: The compiled machine code (OAT/VDEX files)
    # This is often 2x to 3x the size of the original DEX
    compiled_code_size = apk_uncompressed * dex_optimization_multiplier

    # Layer 4: Total system footprint
    total_installed_size = extracted_size + compiled_code_size

    return {
        "Download Size": download_size,
        "Extracted Size": extracted_size,
        "Compiled Code Size": compiled_code_size,
        "Total Reported Size": total_installed_size
    }

# Simulation based on the user's input
result = calculate_android_footprint(
    apk_compressed=7.0, 
    apk_uncompressed=13.4, 
    dex_optimization_multiplier=1.1  # Simplified multiplier
)

print(result)

How Senior Engineers Fix It

Senior engineers don’t just look at the APK; they look at the Profile-Guided Optimization (PGO) and the App Bundle strategy.

  • Android App Bundles (.aab): Instead of a single APK, use App Bundles. This allows Google Play to serve split APKs containing only the resources (densities, languages) specific to a single device, drastically reducing the “Extracted Size.”
  • Profile-Guided Optimization (PGO): Use Cloud Profiles. By collecting usage data from real users, the system knows which parts of the code are actually used. It then only performs AOT compilation on those “hot” methods, preventing the massive bloat of compiling the entire codebase into machine code.
  • Resource Shrinking and R8: Implement aggressive code shrinking and resource stripping using R8 to ensure that the DEX files—which serve as the base for the compiled OAT files—are as lean as possible.

Why Juniors Miss It

  • Focusing on the wrong metric: Juniors often spend all their time optimizing the APK size (the download) while ignoring the Installed size (the user experience).
  • Ignoring the Runtime: Most junior developers treat an app as a static collection of files. They fail to realize that an Android app is a dynamic entity that changes its physical footprint on the disk after the installation process is complete.
  • Lack of Tooling Knowledge: Juniors often check sizes using standard file explorers, whereas seniors use dumpsys package or profman to inspect how the Android Runtime is actually treating the application’s bytecode.

Leave a Comment