There are several reasons you, as a developer, should be concerned about the size of your application and be actively trying to reduce APK size as much as possible. For one thing, there are potential users of your application who might be limited by their data plan, connectivity, or storage space on their phones. Offering an application with size of tens or hundreds of megabytes could potentially make them think twice before downloading your app and maybe even go for a different option just to save the bandwidth.
In this post which is the first part of the series, we’ll have a look at what the APK archive consists of and how to optimize its size by applying a few simple rules to your resource usage. First of all, let’s describe the APK file format and analyze what’s hides inside of it.
APK file format
APK (Android application package) is the package file format for distribution and installation of Android mobile apps. It contains all of the application’s code, resources, assets, manifest file and other application’s files. APK is a type of archive file, specifically it is based on the JAR and ZIP file formats.
Application size
When thinking of the APK size, most people think of the file size of the APK generated by Android Studio. This is the raw APK size. Although this is technically correct, there are other aspects one should take into consideration concerning the size of your Android application.
When users download your application from Google Play, they get the APK generated by your IDE. The APK however contains some of the files uncompressed. This is intentional, and in certain cases even saves space. For example, shared libraries can stay inside of the APK file and be mapped to the memory directly when uncompressed. It also helps the Google Play delta algorithm to produce smaller diffs. Google Play however gzip compresses the whole APK file before it’s downloaded, so bandwidth is saved. This is the download size.
Another important size factor is the size of installed application. The total amount of storage space your application consumes consists of the raw APK size, *.oat files (pre-compiled byte code to native code), uncompressed native libraries (this is optional on Android M+), and also files created at runtime (cache, images, database files, etc.).
I’m sure all of you can think of at least one example of an application that releases its updates every other few days with its size in tens of megabytes. Even though Google Play constantly improves its delta algorithms in order to decrease the size of updates download, if you don’t follow a few basic rules, even the differential update takes a huge amount of space and as always, the smaller the application size, the better. This is the update size. Not long ago, Google released a simple tool to help you estimate the size of patch for your application update. So try and use that.
All in all, from the explanation above, you can see that there are a lot of cases the application size matters and following instructions how to reduce APK size can be beneficial for the users not only during the first install of your application.
APK contents
In order to reduce APK size of your application, first you need to know precisely what the APK archive consists of. As the APK file format builds upon ZIP file format, it can be easily extracted using for example command unzip:
unzip app-release.apk
The content of the archive is in most cases this:
- AndroidManifest.xml – this file is a binary representation of your application manifest. Google Play uses information from it to decide whether the application can be installed on given devices or not;
- assets/ – this folder contains all the assets you’ve included into your application;
- classes.dex – contains compiled application source code into bytecode. There might be more than one of those files, depending on the number of methods in your code. From Android M+, these are compiled into OAT files by the ahead-of-time compiler at install time;
- libs/ – this folder contains any included native libraries divided into folders by ABI (CPU architecture);
- META-INF/ – this folder is present in a signed APK and contains a list of all files in the APK together with their signatures;
- res/ – this folder contains most XML resources (in binary form) and drawables;
- resources.arsc – file containing flattened and compiled identifiers and other resources. It is stored uncompressed, so that the content can be mapped into memory directly.
Reduce APK size: Graphics resources
After a bit of theory, let’s get our hands dirty and start optimizing. We’ll begin with graphics resources which are usually the most storage-demanding part of the Android application. Don’t forget that it’s not only the image resources bundled with your application that matter. If you optimize graphics downloaded from the server (such as profile pictures, article photos, etc.), you save your users a considerate amount of bandwidth.
Optimize PNG and JPG files
There is probably no Android application not using graphics resources, even if it’s just a few icons. These graphic files add to the APK size when it’s built. So it’s worth the extra effort to shrink the size of the graphics as much as possible.
For PNG files, there are a lot of utilities that optimize the files without losing image quality. To name a few examples: pngcrush, pngquant, or zopflipng might be good candidates to try out. Using them can make your PNG files size tens of percents smaller.
For JPG files, there’s for example packJPG tool that compresses them into a more compact form.
It is worth noting that Android tools might in fact increase the size of your optimized graphics as they don’t use such advanced methods of compression. If you optimize your graphics manually, it might be a good idea to disable the built-in cruncher in the build.gradle file:
android { aaptOptions { cruncherEnabled = false } }
WebP
Another great way of reducing the storage requirements for your graphics is to convert them to a WebP format. It is suitable for replacing both JPG and PNG files and often produces much smaller images.
Being quite a new format though, it is only supported in Android 4.0+. Newer features (such as transparency and lossless compression) are supported since Android 4.2.1+ only.
Vector drawables
Since Android SDK v21, Android supports a new type of drawable called VectorDrawable. They are similar to SVG vector format and can be converted from SVG using Android Studio. Replacing bitmaps with vector graphics when appropriate can bring huge savings in storage size.
If you want to support older Android releases, AppCompat makes this available since version 23.2. Using VectorDrawableCompat allows to bring vector drawables back to Android SDK 7+.
Shape drawables
One of the not very often used Android features that has been available in the framework since the beginning is the shape drawable. Using XML, simple shapes can be created, like rectangles, ovals, lines and rings. Available shape parameters are for example gradients, rounded corners, stroke effects, etc.
An example of a shape drawable is this background with gradient and rounded corners for an ImageView:
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <gradient android:startColor="#ff0000" android:endColor="#245ccc" android:angle="45"/> <padding android:left="6dp" android:top="6dp" android:right="6dp" android:bottom="6dp" /> <corners android:radius="8dp" /> </shape>
Reusing resources
Reusing graphics resources instead of providing every possible variant as a separate bitmap could help you reduce APK size of your application too. For example, Android provides several tools to help you re-colour an image. On Android L+ it is the android:tint parameter, while on older versions you can use the ColorFilter class.
Another great example how to save precious space is to reuse appropriate images with the help of RotateDrawable. For example an arrow heading the opposite direction from the original could be created like this:
<?xml version="1.0" encoding="utf-8"?> <rotate xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/arrow_up" android:fromDegrees="180" android:pivotX="50%" android:pivotY="50%" android:toDegrees="180" />
Shrinking resources
Sometimes you find yourself forgetting to remove a no longer resource from your application resources directory. This forgotten item is however still distributed with your application. To analyze and strip the potential resources that are never included in your layouts, drawables, or code, add the following line to your gradle.build file:
android { buildTypes { release { minifyEnabled true shrinkResources true } } }
Another simple method of removing unused resources is to configure your application to bundle only the resource sets that you specify. Imagine, a lot of libraries come with string resources translated into several languages (for example the support library). You can use the resConfigs option in Gradle to restrict the configurations to be included in the APK:
android { defaultConfig { resConfigs "en", "cs" } }
This will remove any resources specified for languages other than the stated in Gradle configuration above, further helping to reduce APK size of your application.
Conclusion
In this post, several tips on how to reduce APK size have been introduced. The various size of an application in different cases has been discussed and the content of the APK files has been described.
This post’s tips focused on application resources and graphics mostly. In the following post, we’ll have a look on how to reduce APK size of your application further by optimization of your code and libraries.