Android MultiDex: Solutions to the 64K methods limit
If you're an Android developer, chances are you've hit the DEX 64K methods limit at some point. This post will walk you through how to identify the source of the problem and various ways of solving it.
From the Building and Running page of the official documentation:
Apps are limited to a 64K method reference limit. If your app reaches this limit, the build process outputs the following error message: Unable to execute dex: method ID not in [0, 0xffff]: 65536
Problem
The underlying problem is imposed by the current set of Dalvik instructions. Specifically, any of the invoke-* methods. These expect a 16-bit argument, representing the target method's reference index. Being limited to 16-bits, valid values are between 0 and 65536. This means, that if you end up calling more than 65,536 defined methods, you are going to exceed this limit.
In many cases, these high numbers are reached by including 3rd-party libraries. Here is a summary of some popular ones:
{: .table}
| Name | Maven Artifact | Overall Method Count |
|----------------------|-----------------------------------|:--------------------:|
| Google Play Services | play-services:6.1.11 | 29,460 |
| Guava | guava:18.0 | 14,842 |
| AppCompat V7 | appcompat-v7:21.0.0 | 12,324 |
| Android Support V4 | support-v4:21.0.0 | 8,078 |
| Jackson | jackson-jaxrs-json-provider:2.4.3 | 6,731 |
| Joda-Time | joda-time:2.5 | 5,025 |
| Apache Commons Lang3 | commons-lang3:3.3.2 | 3,582 |
| Apache Commons IO | commons-io:2.4 | 1,571 |
| Gson | gson:2.3 | 1,243 |
Solutions
At the time of this writing, the official documentation suggests the following solution:
To avoid this, you can load secondary dex files at runtime and use ProGuard to strip out unnecessary class references.
The first part refers us to an old blog post, back from July 2011. TL:DR; An example of how to package additional DEX files into an APK file, and how to load the classes it contains dynamically at runtime using the DexClassLoader. While this might be interesting and fun to read, the post gives instructions of how to do that in an Android project based on Apache Ant. Applying the same workaround to a Gradle-based project involves a few additional tweaks to the build process. In addition, it is no longer required to use DexClassLoader directly, recent additions to the support library can help us with that. The second part (ProGuard) will be discussed in the following sections.
MultiDex
MultiDex allows you to use multiple DEX files contained within one APK. With a few steps, your classes will split automatically into several DEX files (classes.dex, classes2.dex, classes3.dex, etc) in case you reach the method index limit. Information on how to integrate MultiDex into your application will follow.
The first step would be enabling multi-dex mode in your builds. A component of the Android build tools named dx, which is executed during the build process as part of the dex task now accepts a --multi-dex flag. Description:
Add --multi-dex options to dx command line to allow the generation of several dex files when method index limit is about to be reached.
In order to achieve that, add the following to your build.gradle file:
This essentially modifies all dex* tasks (i.e. dexDebug, dexRelease), and adds the --multi-dex flag to the arguments list when executing dx.
An important thing to note about this approach is that the --multi-dex option is incompatible with pre-dexing of library projects. So if you are using any (and you probably are), you will get the following exception at some point of the build:
Pre-dexing is the process of iterating through all of the project's modules and converting them from Java bytecode into Android bytecode. This is done prior to the actual builds and according to a certain logic. It eliminates the need to perform this task during the regular builds, and by that makes builds faster. In order to disable pre-dexing, the following needs to be added to the root build.gradle file of your project. The reason behind putting it in the root build file is to apply the change to all of your sub-projects:
At this point, a build should generate an APK file with multiple DEX files (in case you have reached the method index limit). The next step would be loading these classes at runtime. For Android L or higher (5.0+) this is natively supported, for devices running API level 20 or lower, we need to make another small effort. If you've read the original blog post mentioned above, you know that doing so requires jumping through some hoops together with the DexClassLoader. Luckily, the Android Support Library (revision 21) is shipped with MultiDex support. The package provides a utility that will take care of the class loading process from the additional DEX files on API levels 4 through 20. For API 21 or higher it does nothing, again, as it is natively supported. The package has the following description:
Monkey patches the application context classloader in order to load classes from more than one dex file. The primary classes.dex
must contain the classes necessary for calling this class methods. Secondary dex files named classes2.dex
, classes3.dex
... found in the application APK will be added to the classloader after first call to install(Context).
This library provides compatibility for platforms with API level 4 through 20. This library does nothing on newer versions of the platform which provide built-in support for secondary dex files.
Grab the android-support-multidex.jar file from your SDK folder under the extras/android/support/multidex/library/libs folder, and add it as a dependency. Then you can use it in one of the following ways.
If you don't have a custom Application class registered, just set the following attribute on your application element in the manifest file:
However, if you do have a custom Application class registered, you can either make that class extend MultiDexApplication instead of Application, or simply override attachBaseContext() like so:
From this point and forward, your project should no longer be affected by the 64K limitation.
ProGuard
ProGuard is a tool that shrinks, optimizes and obfuscates your code by removing unused code and renaming classes, fields and methods with semantically obscure names.
By default, ProGuard only runs for release builds. Once executed, it strips away any unnecessary class references, a thing which results eventually in a lower method count. It is possible to activate ProGuard for debug builds as well, while disabling the code obfuscation feature. Bear in mind, this will probably have a time-penalty on the builds, though.
JarJar
Jar Jar Links is a utility that makes it easy to repackage Java libraries and embed them into your own distribution.
JarJar can be used to repackage existing binaries, giving you the ability to remove any packages you want from any library. A particular use case amongst Android developers is to strip away any unused parts of the Google Play Services jar.
Conclusion
At this point, it seems like Google Play Services is perhaps the biggest common threat on your methods count. I would love to see this API broken into separate modules. A detailed summary of this problem was published a while ago by Jake Wharton, so feel free to dive into that as well.
Hopefully, you now have a better knowledge of the workarounds presented above. And if you have followed along, and no longer bound to the 64K limit, it still doesn't mean you shouldn't be mindful about the size of your APK. Try removing any unnecessary dependencies, use ProGuard (at least for release builds) to optimise and obfuscate your code (which also has some security related benefits). And use other techniques to reduce the APK, regardless of the methods count. Another interesting blog post was published a while ago by Cyril Mottier, illustrating how to reduce APK size using various different techniques.