I am the maintainer of https://github.com/creativepsyco/secondary-dex-gradle/ and I am a gradle n00b, therefore I chose the path of BASH scripts although I think it can be done directly in the build file. OR can be refactored to run as a plugin, i might do that when I am upto to terms with Gradle. Here is the reason for my logic.
In order to understand how to split the DEX you must know the build system task order. If you are using gradle then you must know that there are a series of tasks injected inside the build cycle.
For example:
:sdk:processReleaseJavaRes UP-TO-DATE
:sdk:packageReleaseJar
:sdk:compileReleaseNdk UP-TO-DATE
:sdk:packageReleaseJniLibs UP-TO-DATE
:sdk:packageReleaseLocalJar UP-TO-DATE
:sdk:packageReleaseRenderscript UP-TO-DATE
:sdk:packageReleaseResources UP-TO-DATE
:sdk:bundleRelease
:app:prepareComAndroidSupportAppcompatV71910Library UP-TO-DATE
:app:prepareComFacebookAndroidFacebook3141Library UP-TO-DATE
:app:prepareDebugDependencies
:app:compileDebugAidl UP-TO-DATE
:app:compileDebugRenderscript UP-TO-DATE
:app:generateDebugBuildConfig UP-TO-DATE
:app:generateDebugAssets UP-TO-DATE
:app:mergeDebugAssets UP-TO-DATE
:app:generateDebugResValues UP-TO-DATE
:app:generateDebugResources UP-TO-DATE
:app:mergeDebugResources UP-TO-DATE
:app:processDebugManifest UP-TO-DATE
:app:processDebugResources UP-TO-DATE
:app:generateDebugSources UP-TO-DATE
:app:compileDebugJava
:app:preDexDebug
:app:dexDebug
:app:processDebugJavaRes UP-TO-DATE
:app:validateReleaseConfigSigning
:app:packageDebug
:app:zipalignDebug
:app:assembleDebug
In order to do Dexing you should be able to be able to inject your custom task between the dex* and the process* tasks. If you can do this, then Multiple DEXing becomes easy.
The Bash script here essentially does this, if you examine the debug task, it will basically:
- Get the Library Jar file to dex, usually it is build specific & exists in the
exploded-aar
folder for Android Libraries & run the DEX tool on it
- Copy this over to the assets folder, which exists in the final libs folder to be packaged inside the app
- All the library resources etc are already merged, which means there is a need to unzip & zip the file again.
In the gradle build script
// For Debug simply remove the library from getting dex and create it
//----------------------- Extra Debug Step ----------------//
def libraryFiles = new ArrayList<?>()
def secondaryFile = new ArrayList<?>()
variant.dex.libraries.each {
File file ->
if (!file.absolutePath.contains("lib/unspecified/classes.jar")) {
libraryFiles.add(file)
} else {
secondaryFile.add(file)
}
}
variant.dex.libraries = libraryFiles
//----------------------- Extra Debug Step ----------------//
packagingTask.dependsOn variant.javaCompile
}
This manually removes the library from getting dexed, so that it can be generated via the bash script.
I think you can figure out the dexing during the release process in the same way. The other important thing to note is that the Proguard Task is controlled by android gradle plugin and you can't change much about it. Issue with Proguard Rules:
- Each pass of proguard is different, we don't want to end up in a situation where our 2 DEXes have different proguard mappings
- This leaves us in a situation where we cannot proguard our libraries, but this is not really desirable.
- Must generate the dex file after proguard to make sure the mappings are the same. Gradle has no support to merge assets after Proguard (We want to put the dex files in the assets folder)
The other important chunk of code resides in SecondaryDex.java which essentially loads up the second dex file & injects the path of DEX file into the runtime class path. You can optimize this and just inject the path instead of reading the DEX file everytime the app is resumed.
I did the secondary Dex experiment on Google Play Services (which adds 20K methods) and was able to separate into a separate DEX file. This way my main dex file is unaffected by the bloat in Google Play services.
To understand how the Gradle task cycle works, you can refer to the BasePlugin.groovy source, you can see that it is difficult to control some aspects until there is a proper API for accessing variant objects and build tasks.