Shrinking Your Code
“Your code” refers to both the stuff that you write yourself and the stuff that comes from the dependencies that you need (including transitive dependencies).
Probably you use all the code that you wrote yourself. Otherwise, why did you write it?
However, it is very likely that you are not using all of the features from the dependencies that you are using. Any unused code is simply taking up space in your APK, and so it would be nice to get rid of it.
Unfortunately, that is a bit tricky.
A Tale of Two Tools
Finding unused code in a Java/Kotlin codebase requires examining all of the code, what it refers to, and from there determining what isn’t referred to. Then, we need to actually remove the unused code from what goes into the APK, while not actually affecting the dependencies themselves.
The original tool for this process was ProGuard. ProGuard has been around for nearly two decades, and the Android build tools integrated it fairly early on to help reduce APK size.
More recently, Google has switched to their own tool, R8. R8 is actually the compiler, and it handles identifying unused code as part of the compilation process.
Enable Code Minification
However, by default, eliminating unused code (“minification”) is disabled. Partly, that is for build speed, as finding unused code is a time-consuming process. However, part of the reason why it is disabled is because sometimes the tools will think something is unused when in reality it is not.
To opt into code minification, we use minifyEnabled true
in our module’s build.gradle
file, typically only for the release
build type:
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
// other stuff here
}
Here, we have a buildTypes
closure, where we configure the release
build. In there, we have minifyEnabled true
, to override the default and ask that R8 try to remove unused code.
You are welcome to enable minification for debug
builds as well, using a similar closure. Minification takes time, and so for larger projects it may become impractical to use minification for the builds that you want to do several times per day.
Test and Adjust
Once you enable minification, you will need to test your app, for whatever build types you elected to use minifyEnabled true
. This means both your automated tests and manual tests.
If you get ClassNotFoundException
, MethodNotFoundException
, or NoSuchFieldError
crashes with your minified build, whereas you do not in a regular build, that indicates that minification went too far and removed things that you really are using.
The proguard-rules.pro
file that (probably) is in your module’s directory is a place to put rules that will be used by R8 (or ProGuard) to configure the minification process. In particular, -keep
rules will tell R8 to keep classes, methods, or fields that it might otherwise try to remove:
-keep class net.sqlcipher.* { *; }
-keep class net.sqlcipher.database.* { *; }
These lines, for example, tell R8 to keep classes, methods, and fields in the net.sqlcipher
and net.sqlcipher.database
packages. The *
in the fully-qualified class name will match all classes in the package, and the { *; }
will match all methods and fields. Or, you can provide names of specific classes, etc. that you want to keep.
You will need to identify and create -keep
rules like these to get R8 to not remove the things that, when removed, cause the aforementioned crashes.
Remove ABIs
Some of your dependencies might have “native code” (C/C++ libraries) in addition to Java/Kotlin code. These will show up in a lib/
directory inside the APK Analyzer output:
This native code could be large — in this case, it is nearly 1MB. Frequently, the native code ships for multiple CPU architectures (or ABIs). In this case, we see four:
x86_64
x86
arm64-v8a
armeabi-v7a
The vast majority of Android devices use one of those latter two ABIs, for ARM CPUs (32-bit and 64-bit). Your emulator probably uses one of the x86 ABIs, but for a production release, you may be able to ignore the emulator.
You can eliminate native code for CPU architectures that you do not need by adding an ndk
closure to your module’s build.gradle
file, with an abiFilters
declaration:
android {
defaultConfig {
// other stuff goes here
ndk {
abiFilters 'arm64-v8a', 'armeabi-v7a'
}
}
}
abiFilters
is where you can list the ABIs that you want to keep. Others will be removed as part of the packaging process.
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.