Smaller APKs with Classy Shark


A big thanks to my friend Enrique López Mañas for giving me feedback on how to improve this post, and of course Boris Farber for creating Classy Shark.

App Size

An app I am working on feels a lot larger than it should be, my hunch that this is due to dependencies that are being included in the APK that aren’t needed. Using Classy Shark I was able to start reducing the APK size.

Sometimes we need to use a library that depends on other libraries:

Overview

Transitive Dependencies

Those other dependencies are called Transitive Dependencies:
Transitive Dependencies

Sometimes we are using a really small portion of a library so we don’t need all of those other transitive dependencies. Normally proguard would remove these, but if the primary library had a rule for keeping them then they will end up in your apk as well. Meaning a larger APK and a larger dex count.

What is Dex Count?

Android application (APK) files contain executable bytecode files in the form of Dalvik Executable (DEX) files, which contain the compiled code used to run your app. The Dalvik Executable specification limits the total number of methods that can be referenced within a single DEX file to 65,536, including Android framework methods, library methods, and methods in your own code. Getting past this limit requires that you configure your app build process to generate more than one DEX file, known as a multidex configuration.

If we keep our dex count low we can avoid having to add multidex support in our build files. With the side benefit of having simpler gradle files.

Going back to my APK diagram, let’s say that I only want to keep the following dependencies:
Kept Dependencies

In the case of the app I am working on there is nothing pertaining to bar code scanning. The app doesn’t need this
zxing library it’s just taking up space in the app. Unfortunately we don’t always have control over the primary library, fortunately we can still keep those transitive dependencies out of our APKs.

What to exclude

The first step is to identify what to excluded. There are a couple of ways to do this, you can use gradle to take a look at your dependencies. This can be done with the following command:

gradle -q dependencies app:dependencies --configuration compile

This will show you the package dependency tree which will look something like the following but probably a lot longer(as was my case).

compile - Classpath for compiling the main sources.
+--- com.android.support:appcompat-v7:23.1.1
|    \--- com.android.support:support-v4:23.1.1
|         \--- com.android.support:support-annotations:23.1.1
+--- com.mycompany:custom-scanner-library:23.1.1
+--- com.android.support:design:23.1.1
|    +--- com.android.support:appcompat-v7:23.1.1 (*)
|    +--- com.android.support:recyclerview-v7:23.1.1
|    |    +--- com.android.support:support-annotations:23.1.1
|    |    \--- com.android.support:support-v4:23.1.1 (*)
|    \--- com.android.support:support-v4:23.1.1 (*)
+--- com.android.support:cardview-v7:23.1.1
+--- com.android.support:recyclerview-v7:23.1.1 (*)
\--- com.squareup.retrofit:retrofit:2.0.0-beta2
     \--- com.squareup.okhttp:okhttp:2.5.0 -> 2.6.0
          \--- com.squareup.okio:okio:1.6.0

Non Obvious Packages

This is a nice representation of the dependencies. Unfortunately I wasn’t seeing the zxing library, and I was reasonably certain it was being included in the APK even though it wasn’t being used. This is where Classy Shark comes in handy. Classy Shark shows us actual java packages that are in the dex files along with their corresponding method counts.

Running Classy Shark

Simply go grab the jar file and run the following command:

java -jar /Users/jamesbaca/Downloads/ClassyShark.jar

shark

Click open and select your APK from the directory list.

Screen Shot 2016-04-03 at 4.34.26 PM

In my particular case Classy Shark showed me that com.google.xzing was included in my apk but this wasn’t shown in gradles dependency tree. Fortunately the package name indicated that it was a custom scanner library which was enough for me to know that his wasn’t needed to be packaged in the APK.

Excluding Dependencies

We can use Gradle to outright prevent packages from being included. For example if the group and module that contained xzing was called com.mycompany:custom-scanner-library I could exclude it using the following:

configurations {

   compile.exclude group: 'com.mycompany', module: ‘custom-scanner-library’

}

There are other options too. If you want you can exclude by only group as well:

configurations {

   compile.exclude group: 'com.mycompany'

}

Or even by module name only:

configurations {

   compile.exclude module: ‘custom-scanner-library’

}

Note that this is only removing the dependency from the compile configuration(and inheriting configuration). If you wanted to remove it from all configurations you could do so with the following:

Configurations{
    all*.exclude group: ‘com.mycompany’, module: ‘custom-scanner-library’
}

Work In Progress

In this particular example the resulting APK size wasn’t changed dramatically by excluding zxing. The original APK size was 32.4MB and the resulting APK size was 32.2MB. There was a much larger dex count change going down by 1811 methods. While this isn’t a big file size change keep in mind that it was only one dependency that was pulled out and I haven’t reviewed the full list. Additionally every byte counts(especially in markets where bandwidth costs are high). As well by keeping our APK small we can avoid having to deal with extension APKs.

Comments 1

Leave a Reply

Your email address will not be published. Required fields are marked *