Dex is the name of the file format and encoding to which Android Java code is compiled. Early versions of Android would load and execute dex
binaries directly in a virtual machine named Dalvik. More recent versions of Android use the Android Runtime (ART), which treats dex
files as an intermediate representation and performs further compilations on it prior to running the application.
Dex is a very old file format, in terms of the lifespan of smartphones, and was designed for devices whose main memory was measured in tens of megabytes. The design limitations of those days have remained with us to this day.
The dex
file format encodes a limit to the number of methods that can be referenced in a single binary. Because the portion of the file format that stores the number of references is two bytes long, the maximum number of method references is 0xFFFF
, or 65535. If an application contains more than that number of method references, it will fail to compile.
Google has provided a way around this problem, called Multidex. It has compile-time and run-time components. As its name implies, at compile-time it will divide code between one or more dex
files. At runtime, it will teach the default ClassLoader
how to look up classes from these files.
This approach works well on newer devices, but has some substantial drawbacks. It can increase application startup time dramatically, and on older devices can cause Application Not Responding
failures.
Multidex, while effective, should be avoided if possible.
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:
The first point requires diligence and discipline on the part of the developer. When incorporating third-party libraries, one must consider the size of the library. For example, two popular JSON libraries are Jackson and Gson. Functionally they are quite similar, but Gson tends to see greater use in Android. One reason is that Jackson weighs in around 9,000 methods, whereas Gson contributes 1,900.
There are several tools available to help developers keep track of the size of their application:
In order to enable a multidex configuration you need:
MultiDexApplication
or enable the MultiDex in your Application
classIn app/build.gradle
add these parts:
android {
compileSdkVersion 24
buildToolsVersion "24.0.1"
defaultConfig {
...
minSdkVersion 14
targetSdkVersion 24
...
// Enabling multidex support.
multiDexEnabled true
}
...
}
dependencies {
compile 'com.android.support:multidex:1.0.1'
}
Then proceed with one of three options:
When these configuration settings are added to an app, the Android build tools construct a primary dex (classes.dex) and supporting (classes2.dex, classes3.dex) as needed.
The build system will then package them into an APK file for distribution.
Use this option if you don't need an Application
subclass.
This is the simplest option, but this way you can't provide your own Application
subclass. If an Application
subclass is needed, you will have to switch to one of the other options to do so.
For this option, simply specify the fully-qualified class name android.support.multidex.MultiDexApplication
for the android:name
property of the application
tag in the AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.multidex.myapplication">
<application
...
android:name="android.support.multidex.MultiDexApplication">
...
</application>
</manifest>
Use this option if your project requires an Application
subclass.
Specify this Application
subclass using the android:name
property in the manifest file inside the application
tag.
In the Application
subclass, add the attachBaseContext()
method override, and in that method call MultiDex.install()
:
package com.example;
import android.app.Application;
import android.content.Context;
/**
* Extended application that support multidex
*/
public class MyApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
}
Ensure that the Application
subclass is specified in the application
tag of your AndroidManifest.xml:
<application
android:name="com.example.MyApplication"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name">
</application>
The dexcount plugin counts methods and class resource count after a successful build.
Add the plugin in the app/build.gradle
:
apply plugin: 'com.android.application'
buildscript {
repositories {
mavenCentral() // or jcenter()
}
dependencies {
classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.5.5'
}
}
Apply the plugin in the app/build.gradle
file:
apply plugin: 'com.getkeepsafe.dexcount'
Look for the output data generated by the plugin in:
../app/build/outputs/dexcount
Especially useful is the .html chart in:
../app/build/outputs/dexcount/debugChart/index.html
This is very similar to using an Application
subclass and overriding the attachBaseContext()
method.
However, using this method, you don't need to override attachBaseContext()
as this is already done in the MultiDexApplication
superclass.
Extend MultiDexApplication
instead of Application
:
package com.example;
import android.support.multidex.MultiDexApplication;
import android.content.Context;
/**
* Extended MultiDexApplication
*/
public class MyApplication extends MultiDexApplication {
// No need to override attachBaseContext()
//..........
}
Add this class to your AndroidManifest.xml exactly as if you were extending Application:
<application
android:name="com.example.MyApplication"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name">
</application>