Reducing Android App Size for release on Play store

Most android devices these days come with enough internal memory to install countless apps without a second thought. As a mobile app developer this could be seen as a license to not bother about the size of your app however bloated it is. But remember there are many people who still use devices with very less memory, especially in emerging economies. In these devices every mega byte counts and every bit transferred over network costs. The other problem is one of user fatigue: with a million apps on appstore, if your app tests your user patience on download you can be rest assured that the app wont be installed. So it is essential to make a smaller apk and delight your users with quicker installation.

There are two main reasons that contribute to larger apk sizes. The app source code and resources. So let's examine a few ways to put apks on diet.

Enable Proguard to minify the code

Android Studio uses gradle build tool for building application.
More about gradle can be found here

To enable proguard in your gradle build file, add minifyEnabled true to the release build type in your build.gradle file.
Proguard removes unused code and resources in the generated build. It also optimizes the bytecode, removes unused code instructions and obfuscates the remaining classes, fields, and methods with short names. The obfuscated code makes your APK difficult to reverse engineer.

Avoid using proguard with debug builds as it slows the down the build times.

android { buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile(‘proguard-android.txt'), 'proguard-rules.pro' } } ... }

The proguardFiles define the proguard rules. Rules specifying which classes to keep and which to remove.

The proguard-android.txt has the default proguard rules (which most of the time is sufficient to remove unused code) from Android SDK and proguard-rules.pro can used to add custom Proguard rules.

Sometimes it is possible that proguard removes code (Eg. If a class is referenced only by reflection) which is required by your app. To avoid that you can specify classes to be not removed in the proguard conf file.

To force ProGuard to keep certain code, add a -keep line in the ProGuard configuration file. For example:

-keep public class MyClass

This will shrink, optimize and obfuscate the MyClass class but won't remove it.

Similarly -dontwarn specifies not to warn about unresolved references and other important problems at all. ProGuard doesn't print warnings about classes with matching names.

-dontwarn android.support.**

This is an indirect way to let proGuard know that it's ok that the library references some classes that are not available in all versions of the API and hence there won't be warnings for the classes that match the regular expression android.support.**

But ignoring warnings can be dangerous. For instance, if the unresolved classes or class members are indeed required for processing, the processed code will not function properly. Only use this option if you know what you're doing!

More details on this here

Keep it clean

Remove all unused dependency libraries. Keep the code base clean and up-to-date and monitor it at regular intervals for unused libraries. It is easier to do this while starting a new project and get’s increasingly difficult as the project goes on.

Play services apis - Use what's necessary

Selectively compile Google Play service apis into your app rather than compiling the entire play services library. For example, to include only the Google Fit and Android Wear APIs, replace the following line in your build.gradle file:

compile 'com.google.android.gms:play-services:9.2.0'

with these lines:

compile 'com.google.android.gms:play-services-fitness:9.2.0' compile 'com.google.android.gms:play-services-wearable:9.2.0'

Remove unused resources from the app
  • To remove unused resources from the project folder that are not actually referenced in any layouts, drawables, code, etc., enable shrinkResources in the build.gradle file. 

android { buildTypes { release { minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile(‘proguard-android.txt'), 'proguard-rules.pro' } } ... }

But if there are specific resources you wish to keep or discard, create an XML file in your project with a <resources> tag and specify each resource to keep with the tools:keep attribute and each resource to discard with the tools:discard attribute. Both attributes accept a comma-separated list of resource names and asterisk character as a wild card in the name. The tools:discard attribute is especially useful when there are different keep.iml files for different build variants and when a resource used in one of the build variant has to be discarded in other variants. 

For example:

<?xml version=1.0" encoding="utf-8"?> <resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*" tools:discard="@layout/unused2" />

Save this file in your project resources at res/raw/keep.xml. The build does not package this file into your APK.

  • But it is always a good idea to keep your project clean by removing the unused resouces from time to time. You can use the android studio’s Analyze menu to inspect unused resources using Lint tool. AnalyzeRun Inspection by Name → enter “Unused resources”. This will show the unused resources in the project.

  • Libraries like Support library and Play services come with a lot of string resouces that are translated to many languagues. But if your app only supports a few languages, then you could remove the resources for languages not specified using resConfigs.

android { defaultConfig { ... resConfigs "en", "fr" } }

This will limit the language resources to just English and French.

You can read more about shrinking code and resources in developer docs.

Optimise the image resources

Compress the png images used in the app using external tools like zopfli for loseless image compression.

Using WebP for images can decrease the image sizes upto 30%. (only for android versions 4.2.1+ though). 

Limit the Density Buckets

Be decisive while using the assets for different densities. I only include assets for hdpi, xhdpi, xxhdpi and xxxhdpi screen densities. The number of devices with mdpi and ldpi densities is very low these days and android can scale these from higher density assets if required.

Also sometimes we include images that are not used often in the app, but they are still required. For those images, add only single variant (xxhdpi or xxxhdpi) in the drawable-nodpi (which will scale automatically for different densities), if you think the scaling issues are minimal. For example, full screen image backgrounds.

Use Vectors

For android version 5.0+, vector drawables can be used instead of pngs of various sizes for different screen densities.

For android versions less than 5.0 set vectorDrawables.useSupportLibrary = true in build.gradle to generate pngs for the vector drawables.

ShapeDrawables

Use ShapeDrawables whenever possible for resources like buttons, gradients etc. Most of the graphic elements can be created using shape drawables hence pngs can be avoided in those cases.

JPEG Compression

When using Jpeg images, determine the appropriate quality setting and image resolution before hand. A simple change in quality to 85% in jpeg compression will do wonders to jpeg file sizes compared to the original file without any perceivable visual difference.

Reuse Resources

Change view transparency instead of having two assets for showing selected and unselected states.
Use the same asset for different icons in the app which only differ in size but look alike.

Clean up Assets

Keep the /asset folder clean and free of unused files. Lint cannot inspect assets, so do it yourself.

Apk Splits

If the apk gets very big, you could also reduce the size by splittng into different apks based on density, CPU architecture etc., and uploading them onto playstore using multiple apk option. The detailed explanation on this can be found on developer docs.

The simplest way to add Multiple-apk support to your app is through a configuration option called splits in build.gradle file. Using splits you create separate apks for different screen densities and CPU architectures. 

android { ... splits { density { enable true exclude “ldpi”, “xxxhdpi” compatibleScreens ‘small’, ‘normal’, ‘large’, ‘xlarge’ } } }

You can enable splitting by adding enable true in build gradle. Then you can exclude (or include) splits for certain configurations from being created and finally specify the compatibleScreens (usually all four buckets).

You can also similarly generate multiple apks with ABI splits for devices with different CPU architectureS.

You can also create multiple apks using flavors in a more flexible way.

Further reading:
Google I/O video on reducing app size

Wojtek Kaliciński's blog post on smaller apks

Want to read about how to do this in an iOS application?
Find here
While you are at it check out our product InAppText that allows your designers and QA to replicate bugs easily

Narayanarao Rayapureddy

Read more posts by this author.