#SmallerAPK, Part 1: Anatomy of an APK
Update 1: We no longer recommend using Zopfli for compressing your APK
If I ask a bunch of developers what their app’s size is, I’m pretty sure most will look at the APK file generated by Android Studio and tell me how much disk space it takes up on their computer. It’s the most straightforward answer and it’s technically correct, but perhaps I should be asking better questions. Consider these for example:
- How much space does your app take up when installed on a user’s device?
- How much network data does the user have to pay for to download and install your app?
- How big is the download size for an update to your app for existing users?
- What is the memory footprint (in terms of RAM used) of running your application?
If you’re a developer with a high end phone on an all-you-can-eat data plan you might be wondering if it’s worth spending your time on optimizing for APK size… Remember, not all devices have the same storage, memory or network connectivity.
There are places in the world where users have to pay for every megabyte of data they download and Wi-Fi hotspots are not readily available on every corner.
Some devices don’t have the internal storage capacity (disk space) to let users install all the applications they need, which means they have to think twice before installing or updating any software. And who knows, these might be your next thousands or million users, so let’s try to make that APK smaller! It will benefit everyone.
Of course, considering the various requirements and constraints we all have to work with, it’s difficult to choose a one-size-fits-all solution. Sometimes, sacrificing initial download size will speed up subsequent updates. In other cases, contrary to intuition, keeping files uncompressed in your APK can reduce the final amount of disk space taken on the device. I will try to highlight these trade-offs and provide an explanation wherever applicable, but ultimately it’s up to you, the developer, to choose a mix of techniques that make sense for your app and your users.
Runtime memory footprint is mostly out of scope for this series of articles. The presented optimizations may have some small side effects for memory usage and performance, both positive and negative. I’ll try to mention any clear downsides when applicable, but it’s left to the reader to measure performance characteristics of your app and make the final call. Remember, #perfmatters!
What’s in an APK
Before I begin talking about how to slim down an app, let’s first look at the file format itself. An APK is really just a ZIP archive, containing files that make up your application. Normally in the APK you will find entries such as:
Contains compiled application code, transformed into Dex bytecode. You might see more than one DEX file in your APK if you are using multidex to overcome the 65536 method limit. Beginning with Android 5.0 which introduced the ART runtime, these are compiled into OAT files by the ahead-of-time compiler at install time and put on the device’s data partition. You can learn how to reduce the size of your dex code in Part 2: Minifying code
This folder contains most XML resources (e.g. layouts) and drawables (e.g. PNG, JPEG) in folders with various qualifiers, like -mdpi and -hdpi for densities, -sw600dp or -large for screen sizes and -en, -de, -pl for languages. Please note that any XML files in res/ have been transformed into a more compact, binary representation at compile time, so you won’t be able to open them with a text editor from inside the APK.
Part 3: Removing unused resources shows how to make sure you’re not wasting space by having stale resources in your project. In Part 4: Multi-APK, ABI and density splits and Part 5: Multi-APK through product flavors we discuss how to partition assets over multiple APKs that target specific device groups based on their hardware characteristics. Part 6: Image optimization, Zopfli & WebP and Part 7: Image optimization, Shape and VectorDrawables deal with various optimization techniques for making images smaller.
Some resources and identifiers are compiled and flattened into this file. It’s normally stored in the APK without compression for faster access during runtime. Compressing this file manually might seem like an easy win, but is actually not a good idea for at least two reasons. One, Play Store compresses any data for transfer anyway and two, having the file compressed inside the APK would waste system resources (RAM) and performance (especially app startup time).
Part 3 will show two methods for making this file a little bit leaner by only including strings for languages that make sense for your app.
Similar to other XML resources, your application Manifest is transformed during compilation into a binary format. Play Store uses certain information contained in the AndroidManifest to decide if an APK can be installed on a device, checking against allowed densities or screen sizes and available hardware and features (such as a touchscreen). If you want to inspect those Manifest entries after compilation, you can use the aapt tool from the Android SDK:
$ aapt dump badging your_app.apk
Any native libraries (*.so files) will be put in subfolders named after the ABI (CPU architecture, e.g. x86, x86_64, armeabi-v7a) that they’re targeting under the libs/ folder. Normally, they are copied out of the APK into your /data partition at install time. However, since the APK itself is never altered while it sits on a user’s device, this essentially doubles the space needed for any native library. Part 8 (native libraries, open from APK) of this article offers a solution for this problem on Android 6.0+, with the added benefit of network bandwidth savings also on older devices.
This folder is used for any file assets that will not be used as Android-type resources. Most commonly this will be font files or game data, like levels and textures, as well as any other application data that you want to open directly as a file stream.
This folder is present in signed APKs and contains a list of all files in the APK with their signatures. The way signing in Android works currently is that it verifies the signatures against uncompressed file contents from the archive, one by one.
This has some interesting consequences. Because every entry in a ZIP file is stored separately, this means that you can change individual files’ compression level without re-signing. The signature verification will fail however if you remove any file from the archive after it is signed.
One more thing to note about how a signed APK is created is that the zipalign tool is used as the last stage of the build. If you change the contents of the archive by hand, normally you will have to re-sign, then zipalign before uploading the APK to the Play Store.
Recompress the APK using Zopfli (don’t do it)
Update: Previously this article contained a section on recompressing files in your APK using a stronger compression algorithm called Zopfli. This feature is now being removed from our build tools as of Android Studio 2.2 and is no longer recommended, as it might interfere with future plans for making Play Store incremental updates even smaller.
If you’re still not convinced to stop using Zopfli for your APK, readers have alerted me to the fact that certain Android 5.0.1 devices might have a problem reading Zopfli-compressed APKs and can even crash your apps.
The next chapters of this guide should be your primary focus anyway and can give 10x more savings than using Zopfli for the APK.
Next up, Part 2: Minifying code!