PSA: fix MultiDex build crashes

PSA: fix MultiDex build crashes
Cover image: “A Farmyard in Normandy” by Claude Monet — on WikiArt

When life gives you lemons, you bump the heap size.

[tl;dr is at the end of the article]

One day, the moment comes. The moment when your builds fail with this error message:

trouble writing output:
Too many field references: 131000; max is 65536.
You may try using --multi-dex option.

What happened? Maybe the latest Play Services update did it. Or maybe it’s the sixth analytics SDK that the client has asked for in the app. Or something else. But one thing’s for sure: your app just joined the >65k methods club.

This would have been a real issue just one year ago. There were some workarounds and some strategies to mitigate the issue, including some crazy dex splitting techniques, but none of them was always working, or something you’d want to do in the first place.


MultiDex?

Luckily enough, since Google has introduced MultiDex, things are awful simple to fix. Drop this in your build.gradle and you’re all set:

android {
  // ...
  defaultConfig {
    // ...
    multiDexEnabled true
  }
}

No hacks (not in your code, anyway), no weird stuff. Clean and easy.

Drawbacks

Multidex has some drawbacks, unfortunately. First of all, it increases build times, which is never good. Secondarily, on Dalvik (but not on ART), it carries a startup time penalty as well while the classloader reads the second dex file from the apk.

But the real penalty is that sometimes it might crash your build. Yes. The build. Not the app.

UNEXPECTED TOP-LEVEL ERROR:
java.lang.OutOfMemoryError: GC overhead limit exceeded
  at com.android.dx.cf.code.ExecutionStack.copy(ExecutionStack.java:66)
  at com.android.dx.cf.code.Frame.makeExceptionHandlerStartFrame(Frame.java:397)
  at com.android.dx.cf.code.Ropper.processBlock(Ropper.java:916)
  at com.android.dx.cf.code.Ropper.doit(Ropper.java:742)
  at com.android.dx.cf.code.Ropper.convert(Ropper.java:349)
  at com.android.dx.dex.cf.CfTranslator.processMethods(CfTranslator.java:280)
  at com.android.dx.dex.cf.CfTranslator.translate0(CfTranslator.java:137)
  at com.android.dx.dex.cf.CfTranslator.translate(CfTranslator.java:93)
  at com.android.dx.command.dexer.Main.processClass(Main.java:729)
  at com.android.dx.command.dexer.Main.processFileBytes(Main.java:673)
  at com.android.dx.command.dexer.Main.access$300(Main.java:83)
  at com.android.dx.command.dexer.Main$1.processFileBytes(Main.java:602)
  at com.android.dx.cf.direct.ClassPathOpener.processArchive(ClassPathOpener.java:284)
  at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:166)
  at com.android.dx.cf.direct.ClassPathOpener.process(ClassPathOpener.java:144)
  at com.android.dx.command.dexer.Main.processOne(Main.java:632)
  at com.android.dx.command.dexer.Main.processAllFiles(Main.java:505)
  at com.android.dx.command.dexer.Main.runMultiDex(Main.java:334)
  at com.android.dx.command.dexer.Main.run(Main.java:244)
  at com.android.dx.command.dexer.Main.main(Main.java:215)
  at com.android.dx.command.Main.main(Main.java:106)

What this rather obscure error means is that the dexing phase of the build has basically ran out of memory.


Fix the issue (tl;dr)

Thanks to someone’s suggestion (I forgot who, I’m sorry, this happened a while ago), after spending quite some time scratching my head, I found out that there is a simple solution for this issue.

Add this to your module’s build.gradle:

android {
  // ...
  dexOptions {
    javaMaxHeapSize "2G"
  }
}

And voilà!