As the Android platform has continued to grow, so has the size of Android apps. When your app and the libraries it references reach a certain size, you encounter build errors that indicate your app has reached a limit of the Android app build architecture. Earlier versions of the build system report this error as follows:
Conversion to Dalvik format failed: Unable to execute dex: method ID not in [0, 0xffff]: 65536
More recent versions of the Android build system display a different error, which is an indication of the same problem:
trouble writing output: Too many field references: 131000; max is 65536. You may try using --multi-dex option.
Both these error conditions display a common number: 65,536. This number is significant in that it represents the total number of references that can be invoked by the code within a single Dalvik Executable (DEX) bytecode file. This page explains how to move past this limitation by enabling an app configuration known as multidex, which allows your app to build and read multiple DEX files.
About the 64K reference limit
Android app (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. In the context of computer science, the term Kilo, K, denotes 1024 (or 2^10). Because 65,536 is equal to 64 X 1024, this limit is referred to as the '64K reference limit'.
Multidex support prior to Android 5.0
Versions of the platform prior to Android 5.0 (API level 21) use the Dalvik
runtime for executing app code. By default, Dalvik limits apps to a single
classes.dex bytecode file per APK. In order to get around this
limitation, you can use the multidex support
library, which becomes part of the primary DEX file of your app and then
manages access to the additional DEX files and the code they contain.
Note: If your project is configured for multidex with
minSdkVersion 20 or lower, and you deploy to target devices
running Android 4.4 (API level 20) or lower, Android Studio disables Instant Run.
Multidex support for Android 5.0 and higher
Android 5.0 (API level 21) and higher uses a runtime called ART which
natively supports loading multiple DEX files from APK files. ART
performs pre-compilation at app install time which scans for
classesN.dex files and compiles them into a single
.oat file for
execution by the Android device. Therefore, if your minSdkVersion
is 21 or higher, you do not need the multidex support library.
For more information on the Android 5.0 runtime, read ART and Dalvik.
Note: While using Instant Run,
Android Studio automatically configures your app for multidex when your app's
minSdkVersion is set to 21 or higher. Because Instant Run only
works with the debug version of your app, you still need to configure your
release build for multidex to avoid the 64K limit.
Avoid the 64K limit
Before configuring your app to enable use of 64K or more method references, you should take steps to reduce the total number of references called by your app code, including methods defined by your app code or included libraries. The following strategies can help you avoid hitting the DEX reference limit:
- Review your app's direct and transitive dependencies - Ensure any large library dependency you include in your app is used in a manner that outweighs the amount of code being added to the app. A common anti-pattern is to include a very large library because a few utility methods were useful. Reducing your app code dependencies can often help you avoid the DEX reference limit.
- Remove unused code with ProGuard - Enable code shrinking to run ProGuard for your release builds. Enabling shrinking ensures you are not shipping unused code with your APKs.
Using these techniques might help you avoid the need to enable multidex in your app while also decreasing the overall size of your APK.
Configure your app for multidex
Setting up your app project to use a multidex configuration requires that you make the following modifications to your app project, depending on the minimum Android version your app supports.
If your minSdkVersion is set to 21 or higher, all you need to
do is set multiDexEnabled to true in your
module-level build.gradle file, as shown here:
android {
defaultConfig {
...
minSdkVersion 21
targetSdkVersion 26
multiDexEnabled true
}
...
}
However, if your minSdkVersion is set to 20 or lower, then you
must use the multidex
support library as follows:
-
Modify the module-level
build.gradlefile to enable multidex and add the multidex library as a dependency, as shown here:android { defaultConfig { ... minSdkVersion 15 targetSdkVersion 26 multiDexEnabled true } ... } dependencies { compile 'com.android.support:multidex:1.0.1' } - Depending on whether you override the
Applicationclass, perform one of the following:If you do not override the
Applicationclass, edit your manifest file to setandroid:namein the<application>tag as follows:<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myapp"> <application android:name="android.support.multidex.MultiDexApplication" > ... </application> </manifest>If you do override the
Applicationclass, change it to extendMultiDexApplication(if possible) as follows:public class MyApplication extends MultiDexApplication { ... }Or if you do override the
Applicationclass but it's not possible to change the base class, then you can instead override theattachBaseContext()method and callMultiDex.install(this)to enable multidex:public class MyApplication extends SomeOtherApplication { @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); MultiDex.install(this); } }Caution: Do not execute
MultiDex.install()or any other code through reflection or JNI beforeMultiDex.install()is complete. Multidex tracing will not follow those calls, causingClassNotFoundExceptionor verify errors due to a bad class partition between DEX files.
Now when you build your app, the Android build tools construct a primary DEX
file (classes.dex) and supporting DEX files
(classes2.dex, classes3.dex, and so on) as needed.
The build system then packages all DEX files into your APK.
At runtime, the multidex APIs use a special class loader to search all of the
available DEX files for your methods (instead of searching only in the main
classes.dex file).
Limitations of the multidex support library
The multidex support library has some known limitations that you should be aware of and test for when you incorporate it into your app build configuration:
- The installation of DEX files during startup onto a device's data partition is complex and can result in Application Not Responding (ANR) errors if the secondary DEX files are large. In this case, you should apply code shrinking with ProGuard to minimize the size of DEX files and remove unused portions of code.
- When running on versions prior to Android 5.0 (API level 21), using
multidex is not enough to work around the linearalloc limit (issue 78035). This limit was increased in
Android 4.0 (API level 14), but that did not solve it completely. And on
versions lower than Android 4.0, you might reach the linearalloc limit before
reaching the DEX index limit. So if you are targeting API levels lower than
14, test thoroughly on those versions of the platform, because your app might
have issues at startup or when particular groups of classes are loaded.
Code shrinking can reduce or possibly eliminate these issues.
Declare classes required in the primary DEX file
When building each DEX file for a multidex app, the build tools perform
complex decision-making to determine which classes are needed in the primary DEX
file so that your app can start successfully. If any class that's required
during startup is not provided in the primary DEX file, then your app crashes
with the error java.lang.NoClassDefFoundError.
This shouldn't happen for code that's accessed directly from your app code because the build tools recognize those code paths, but it can happen when the code paths are less visible such as when a library you use has complex dependencies. For example, if the code uses introspection or invocation of Java methods from native code, then those classes might not be recognized as required in the primary DEX file.
So if you receive java.lang.NoClassDefFoundError, then you
must manually specify these additional classes as required in the primary DEX
file by declaring them with the multiDexKeepFile or the
multiDexKeepProguard property in your build type. If a class is matched in either
the multiDexKeepFile or the multiDexKeepProguard file, then that class
is added to the primary DEX file.
multiDexKeepFile property
The file you specify in multiDexKeepFile should contain one class per line, in the
format com/example/MyClass.class. For example, you can create a
file called multidex-config.txt that looks like this:
com/example/MyClass.class com/example/MyOtherClass.class
Then you can declare that file for a build type as follows:
android {
buildTypes {
release {
multiDexKeepFile file 'multidex-config.txt'
...
}
}
}
Remember that Gradle reads paths relative to the build.gradle file,
so the above example works if multidex-config.txt is in the same directory
as the build.gradle file.
multiDexKeepProguard property
The multiDexKeepProguard file uses the same format as Proguard and supports the entire
Proguard grammar. For more information about Proguard format and grammar, see the
Keep Options section
in the Proguard manual.
The file you specify in multiDexKeepProguard should contain -keep
options in any valid ProGuard syntax. For example,
-keep com.example.MyClass.class. You can create a file called
multidex-config.pro that looks like this:
-keep class com.example.MyClass -keep class com.example.MyClassToo
If you want to specify all classes in a package, the file looks like this:
-keep class com.example.** { *; } // All classes in the com.example package
Then you can declare that file for a build type as follows:
android {
buildTypes {
release {
multiDexKeepProguard 'multidex-config.pro'
...
}
}
}
Optimize multidex in development builds
A multidex configuration requires significantly increased build processing time because the build system must make complex decisions about which classes must be included in the primary DEX file and which classes can be included in secondary DEX files. This means that incremental builds using multidex typically take longer and can potentially slow your development process.
To mitigate the longer build times for multidex output, create two build
variants using
productFlavors: a development flavor and a release flavor
with different values for minSdkVersion.
For the development flavor, set minSdkVersion to 21. This
setting enables a build feature called pre-dexing, which generates
multidex output much faster using an ART format available only on Android 5.0
(API level 21) and higher. For the release flavor, set the
minSdkVersion as appropriate for your actual minimum support level.
This setting generates a multidex APK that is compatible with more devices, but
takes longer to build.
The following build configuration sample demonstrates the how to set up these flavors in a Gradle build file:
android {
defaultConfig {
...
multiDexEnabled true
}
productFlavors {
dev {
// Enable pre-dexing to produce an APK that can be tested on
// Android 5.0+ without the time-consuming DEX build processes.
minSdkVersion 21
}
prod {
// The actual minSdkVersion for the production version.
minSdkVersion 14
}
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
}
dependencies {
compile 'com.android.support:multidex:1.0.1'
}
After you complete this configuration change, you can use the
devDebug variant of your app for incremental builds,
which combines the attributes of the
dev product flavor and the debug build type. That
creates a debuggable app with multidex enabled and proguard
disabled (because minifyEnabled is false by default).
These settings cause the
Android plugin for Gradle to do the following:
- Perform pre-dexing: Build each app module and each dependency as a separate DEX file.
- Include each DEX file in the APK without modification (no code shrinking).
- Most importantly, the module DEX files are not combined, and so the long-running calculation to determine the contents of the primary DEX file is avoided.
These settings result in fast, incremental builds, because only the DEX files of modified modules are recomputed and repackaged during subsequent builds. However, the APK from these builds can be used to test on Android 5.0 devices only. Yet, by implementing the configuration as a flavor, you preserve the ability to perform normal builds with the release-appropriate minimum API level and ProGuard code shrinking.
You can also build the other variants, including a prodDebug variant
build, which takes longer to build, but can be used for testing outside of development.
Within the configuration shown, the prodRelease variant would be the final testing
and release version. For more information about using build variants, see
Configure Build Variants.
Tip: Now that you have different build variants for different
multidex needs, you can also provide a different manifest file for each
variant (so only the one for API level 20 and lower changes the
<application> tag name),
or create a different Application subclass for each variant (so
only the one for API level 20 and lower extends the MultiDexApplication class or calls MultiDex.install(this)).
Test multidex apps
When you write instrumentation tests for multidex apps, no additional configuration is required
if you use a
MonitoringInstrumentation (or an
AndroidJUnitRunner)
instrumentation. If you use another
Instrumentation,
then you must override its onCreate() method with the following code:
public void onCreate(Bundle arguments) {
MultiDex.install(getTargetContext());
super.onCreate(arguments);
...
}