Let's first understand the problem:
On pre-Lollipop devices, only main dex is being loaded by the framework. To support multi-dex applications you have to explicitly patch application class loader with all the secondary dex files (this is why your Application class have to extend MultiDexApplication class or call MultiDex#install).
This means that your application's main dex should contain all the classes that are potentially accessible before class loader patching.
You will receive java.lang.ClassNotFoundException if your application code will try to reference a class that was packaged in one of your secondary dex files before successfully patching application class loader.
I've documented here how plugin decides which classes should be packaged in main-dex.
If total amount of methods that those classes are referencing exceeds the 65,536 limit, then build will fail with Too many classes in --main-dex-list, main dex capacity exceeded
error.
I can think of three possible solutions for this issue:
- (The easiest solution, but not suitable for most of the
applications) Change your minSdkVersion to 21.
- Shrink your application code. This was discussed many times previously (see here and here).
- If none of the above solutions work for you, you can try to use my workaround for this issue - I'm patching the Android gradle plugin to not include Activity classes in main dex. It's a bit hacky, but works well for me.
There's an issue in Android bug tracker regarding this error. Hopefully the Tools team will provide a better solution soon.
Update (4/27/2016)
Version 2.1.0 of Gradle plugin allows to filter main-dex list classes.
Warning: this is using an unsupported api that will be replaced in the future.
For example, to exclude all activity classes you can do:
afterEvaluate {
project.tasks.each { task ->
if (task.name.startsWith('collect') && task.name.endsWith('MultiDexComponents')) {
println "main-dex-filter: found task $task.name"
task.filter { name, attrs ->
def componentName = attrs.get('android:name')
if ('activity'.equals(name)) {
println "main-dex-filter: skipping, detected activity [$componentName]"
return false
} else {
println "main-dex-filter: keeping, detected $name [$componentName]"
return true
}
}
}
}
}
You can also check my example project that demonstrates this issue (and applies the above filtering).
Update 2 (7/1/2016)
Version 2.2.0-alpha4 of Gradle plugin (with build-tools v24) finally solves this issue by reducing multidex keep list to a minimum.
The unsupported (and undocumented) filter from 2.1.0 should not be used anymore. I've updated my sample project, demonstrating that build succeeds now without any custom build logic.