Post_20150120_AndroidDEX_Header

If you are an Android developer reading this, chances are you’ve at least heard of the dreaded 64k method limit in Dalvik. In a nutshell, the issue is that in a DEX file, you can reference a very large number of methods, but you can only invoke the first 65,536 of them, because that’s all the room you have in the method invocation set. If your source code and all those cool libraries that you are using exceed this limit – kaboom!

UNEXPECTED TOP-LEVEL EXCEPTION:

com.android.dex.DexIndexOverflowException: method ID not in [0, 0xffff]: 65536
   at com.android.dx.merge.DexMerger$6.updateIndex(DexMerger.java:502)
   at com.android.dx.merge.DexMerger$IdMerger.mergeSorted(DexMerger.java:277)
   at com.android.dx.merge.DexMerger.mergeMethodIds(DexMerger.java:491)
   at com.android.dx.merge.DexMerger.mergeDexes(DexMerger.java:168)
   at com.android.dx.merge.DexMerger.merge(DexMerger.java:189)
   at com.android.dx.command.dexer.Main.mergeLibraryDexBuffers(Main.java:454)
   at com.android.dx.command.dexer.Main.runMonoDex(Main.java:302)
   at com.android.dx.command.dexer.Main.run(Main.java:245)
   at com.android.dx.command.dexer.Main.main(Main.java:214)
   at com.android.dx.command.Main.main(Main.java:106)

For more detailed information about the issue, read this detailed post on the topic.

Not to be deterred, the amazing Android developer community has come up with a few solutions, like this one from dmarcato, and this one from casidiablo. They work, but required you to do some serious acrobatics.

Eventually, Google decided to release an official solution for this in the form of the MultiDex Support Library which was first announced back in Oct ‘14 and Gradle support was added in v0.14.0 of the Android Gradle plugin a few weeks later.

Though using this library solves the problem, it is not perfect (more on this later). The first thing to do should be to determine how many methods your app currently has and how many of them are being added by each of its dependencies. Earlier you had to do this manually, but now there are a bunch of tools available:

  1. Use a Gradle plugin – We wrote an extremely easy to use Gradle plugin some time back that lists the number of methods by package along with the total number. You can find more information about it here.
  2. www.methodscount.com – Wondering how many methods a particular library will add to your application? Just enter the ‘compile’ statement on this website and it’ll tell you it’s method count, dependencies, JAR size and DEX size.
  3. Android Studio Plugin – This excellent plugin shows the method count of each dependency right inside Android Studio.

Irrespective of what you tool you end up using, use the information it provides to perform an audit of your app’s dependencies. You should look for unused libraries or ones that can be minimised or even replaced with your own simpler solution. If you are not using Proguard to remove unused code, first enable it and see if it solves the problem. Using Proguard is well… not fun, but once you manage to make it work properly, it’ll significantly reduce the method count. If all else fails, you’ll have to use the MultiDex support library.

Using the MultiDex Support Library

If you are using Android Studio, the process is very straight-forward. If you are not, I highly recommend migrating, as Google may soon drop support for the Eclipse ADT plugin and the old Ant based build system.

Step 1

Add the dependency for the MultiDex support library in your build.gradle

dependencies {
...
   compile 'com.android.support:multidex:'
   ...
}

Step 2

Enable multi-dexing by setting the multiDexEnabled flag in the buildType or productFlavor section of your gradle configuration.

defaultConfig {
   ...
multiDexEnabled true
...
}

Now depending on your project, you have 3 options:

If you haven’t created your own Application class, simply declare android.support.multidex.MultiDexApplication as your application class in AndroidManifest.xml

   ....
   android:name="android.support.multidex.MultiDexApplication"
   ...

If you already have your own Application class, make it extend android.support.multidex.MultiDexApplication instead of android.app.Application

If your Application class is extending some other class and you don’t want to or can’t change it, override attachBaseContext() as shown below:

public class MyApplication extends FooApplication {
   @Override
   protected void attachBaseContext(Context base) {
      super.attachBaseContext(base);
      MultiDex.install(this);
   }
}

Whichever you choose, each option generates multiple dex files instead of a single massive one and also takes care of loading all the classes in each one of them at run-time. Neat, right?

Now, when you build the app, Gradle will generate multiple dex files and generate an APK that you can successfully run on a device/emulator.

Post_20150120_AndroidDEX_code

You can see it in action in this sample app on Github.

Caveats

Out of memory issues

For projects with lots of dependencies, the build process may fail with the following error –

Error:Execution failed for task ':app:dexDebug'.
...
   Error Code:
      3
   Output:
      UNEXPECTED TOP-LEVEL ERROR:
      java.lang.OutOfMemoryError: GC overhead limit exceeded
         at com.android.dx.cf.cst.ConstantPoolParser.parse0(ConstantPoolParser.java:326)
...

To fix it, set the following dex options in the ‘android’ closure –

dexOptions {
   incremental true
   javaMaxHeapSize "4g"
}

Slow application startup

Based on our experience with this support library, it works fine in most cases. However, it has a big impact on application launch time with large apps on some devices like the Kindle Fire. Loading all the classes on application launch simply takes a long time, which results in a black screen being displayed for several seconds, or in some cases, ANR errors.

More details, best-practices and limitations about MultiDex can be found here.

Conclusion

While the library fixes the DEX 64K problem in most cases, it should be treated as a last resort. Before attempting to use it, you should audit your project for unwanted dependencies and remove as much unused code as possible using ProGuard. If you do go ahead and use it, make sure you test your app on older devices.