I'm working on a game being exported from Unity as an Android Studio project. When I assemble and run as a debug build, it works as expected. Since we are using multiple 3rd party libraries, there are more than 65K methods, and it generates quite a few DEX files (11 files). Looking in the contents of these DEX files, they are not all full. In fact, most of them contain only a single BuildConfig class, or a bunch of related R classes. In fact, only 2 of the DEX files have anything appreciable in them, classes7.dex and classes11.dex. I don't know how the app even runs; I thought the main activity needed to be in classes.dex for it to work. But in any case everything actually works fine.
However, in release builds, the situation is much, much worse. I'm talking about 109 (one hundred and nine!) DEX files. This appears to simply be much more granular separation of the classes that were originally in the 11 DEX files before, for some reason. And here, things start to break down. On launch, ClassNotFoundExceptions start appearing on some devices, but on others it works fine. The common factor I have seen indicating whether it will work is OS version. All the devices are running Android OS 5.0+, so multidexing is supported natively, but the stable devices are mostly running 6.0+.
The main activity is in classes54.dex, which extends from a class in classes30.dex, which extends from a class in classes106.dex, which extends from Activity. Those classes it can find just fine though. The first class it complains it can't find is over in classes91.dex, for example.
I assume the problem is within the gradle process, since the issue occurs when exporting directly to an APK from Unity or when building within Android Studio. So my question is how do I either:
convince Unity/Android Studio/Gradle to output a sensible number of DEX files, or
Get all devices to look at all the dex files, even when there are 100+, when looking for classes?
Current build.gradle created when exporting from Unity:
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
classpath 'com.google.gms:google-services:3.0.0'
}
}
allprojects {
repositories {
flatDir {
dirs 'libs'
}
google()
}
}
apply plugin: 'com.android.application'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation(name: 'GoogleAIDL', ext:'aar')
implementation(name: 'GooglePlay', ext:'aar')
implementation(name: 'android.arch.lifecycle.runtime-1.0.0', ext:'aar')
//...
//Also included: Google Play, Facebook, Crashlytics, AdMob, Firebase, and more, redacted for convenience
//...
implementation project(':Firebase')
implementation project(':GoogleMobileAdsIronSourceMediation')
implementation project(':GoogleMobileAdsMediationTestSuite')
implementation project(':GoogleMobileAdsPlugin')
implementation project(':GoogleMobileAdsTapjoyMediation')
implementation project(':GooglePlayGamesManifest.plugin')
implementation project(':unity-android-resources')
}
android {
compileSdkVersion 27
buildToolsVersion '28.0.3'
defaultConfig {
targetSdkVersion 27
applicationId 'redacted'
multiDexEnabled true
ndk {
abiFilters 'armeabi-v7a'
}
versionCode 0
versionName '1.0.8'
}
dexOptions {
incremental true
javaMaxHeapSize "4g"
}
lintOptions {
abortOnError false
}
aaptOptions {
noCompress '.unity3d', '.ress', '.resource', '.obb', 'crashlytics-build.properties', 'google-services-desktop.json', 'someotherfiles'
}
signingConfigs {
release {
storeFile file('/path/to/key.keystore')
storePassword 'redacted'
keyAlias 'key'
keyPassword 'redacted'
}
}
buildTypes {
debug {
minifyEnabled false
useProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'
jniDebuggable true
//Explicitly sign with release key anyway
signingConfig signingConfigs.release
}
release {
minifyEnabled false
useProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'
signingConfig signingConfigs.release
}
}
packagingOptions {
doNotStrip '*/armeabi-v7a/*.so'
}
}
Use multiDex when minSdkVersion before 21,should be config multiDexKeepProguard.
link https://developer.android.com/studio/build/multidex#keep
like this
...
multiDexEnabled true
multiDexKeepProguard file("keep_in_main_dex.pro")
...
keep_in_main_dex.pro
-keep class android.support.multidex.** { *; }
# Those classes or methods used in the Application init
....
If use "Run app" button generate apk, the apk may be contains many dex files.
Use "Build->Make Module 'app'" or command line.
I have discovered a... less than optimal solution. My app is currently targeting 21+ as the min SDK. If I drop it down to 20 or lower, the build process apparently changes. Only 2 DEX files come out. Of course this means I need to support Android 4.4+ instead of 5.0+.
To be clear, the only change I made is to add the line
minSdkVersion 20
above targetSdkVersion 27, which changes how it builds. If I change it to minSdkVersion 21 or any number higher, it goes back to being broken.
Use of Pre-dexing is usually the reason for large number of dex files.
Pre-dexing is the process of iterating through all of the project’s modules and converting them from Java bytecode into Android bytecode. It builds each app module and each dependency as a separate DEX file. This dexOption is used in order to build in an incremental way and speed up the build process as change in one module leads to dexing of that module only.
Please try using following dexOptions in your build.gradle file
android {
...
dexOptions {
preDexLibraries = false
}
}
The above should solve your problem and you won't need to support Android 4.4 for your application.
Looks like there is limit of 100 dex files that can be read while targeting minSdkVersion 21: https://android.googlesource.com/platform/art/+/lollipop-release/runtime/dex_file.cc#303. That is the reason your app is working fine after downgrading the api level to 20.
I'm trying to figure out why I can't get a release build to install correctly using Android Studio. This is my buildTypes block:
buildTypes {
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
release {
shrinkResources false
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
and these are my product flavors:
flavorDimensions "version"
productFlavors {
free {
applicationId "com.example.app.free"
dimension "version"
signingConfig signingConfigs.config
}
paid {
applicationId "com.example.app.paid"
dimension "version"
signingConfig signingConfigs.config
}
}
When I install the debug paid or free versions, all is fine and dandy. Nothing wrong at all. When I try to install the release versions (paid or free) I'm getting:
'Execution failed for task ':app:transformDexArchiveWithDexMergerForFreeRelease'. com.android.build.api.transform.TransformException: java.lang.RuntimeException: java.lang.RuntimeException: com.android.builder.dexing.DexArchiveMergerException: Error while merging dex archives: ... Learn how to resolve the issue at https://developer.android.com/studio/build/dependencies#duplicate_classes. Program type already present: com.google.android.youtube.player.YouTubeApiServiceUtil'
My questions is this: Why is the duplicate class exception happening only during release, when I have nothing different defined between the release and debug buildTypes?
EDIT I've solved the issue by removing the YouTubePlayer Library dependency in gradle (b/c apparently my implementation of the google YT service was causing an internal library to be created, so depending on the imported one was redundant?). This still leaves my question valid. Why did the debug work, but not the release when nothing was declared differently?
I had the same issue when linking with another module when I added another (indirect) dependency on its assemble (it was Protobuf module which needs to generate Java files from .proto files before the app module can generate its JSON model). It seems that assembleRelease optimises the code in a way that it may generate slightly different class file depending on where it is called from and the Dex merger then cannot decide which of the class files to use. assembleDebug generates always the same (unoptimised) code which it can merge.
(For anyone having the same problem with Protobuf, the solution for that is to depend on :protobuf:GenerateProto instead of :protobuf:assemble.)
I have created a multidex app. But in regards to proguard i have the following in build.gradle:
android {
defaultConfig {
...
multiDexEnabled true
}
productFlavors {
dev {
// Enable pre-dexing to produce an APK that can be tested on
// Android 5.0+ without the time-consuming DEX build processes.
minSdkVersion 21
}
prod {
// The actual minSdkVersion for the production version.
minSdkVersion 14
}
}
buildTypes {
release {
minifyEnabled true
*** proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
}
dependencies {
compile 'com.android.support:multidex:1.0.1'
}
My question is about the progardFiles vs using multiDexKeepProguard. The documentation states:
File multiDexKeepProguard
Text file with additional ProGuard rules to be used to determine which
classes are compiled into the main dex file.
If set, rules from this file are used in combination with the default
rules used by the build system.
So if i do not use the multiDexKeepProguard then my classes still get compiled but may not end up in the main dex file, is that correct ? I am not clear how this differs from proguardFiles.
Android documentation also references this.
If you're enabling proguard in your application it's usually necessary to define proguard rules. proguardFiles are meant to be the instructions for progurard to minify or obfuscate your app.
multiDexKeepProguard is specifically for telling multidex which files are important to load at app startup and therefore what to keep in the main dex. As far as i'm aware, it just uses the proguard syntax as a convenience. This is optional and will usually only be set if there is an issue at runtime.
As part of the Android Studio 2.2 roll out I updated my Gradle Build tools to v2.2. After doing that my signed APK build process fails because I have shrinkResources = true.
Once I switch back to Gradle v2.1.3 OR set shrinkResources = false everything works fine. Here's my app gradle build file:
android {
signingConfigs {
}
compileSdkVersion 24
buildToolsVersion '24.0.0'
defaultConfig {
applicationId "com.sample.testapp"
minSdkVersion 21
targetSdkVersion 24
versionCode 4
versionName "0.0.4"
}
buildTypes {
release {
minifyEnabled false
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
proguardFile 'C:/Users/code/testapp/app/proguard-rules.pro'
}
}
productFlavors {
}
}
With Gradle set to v2.2 here's the build error I get when generating a signed APK
Does anyone know why this is occurring and if there's a work around? I've Googled around a bit and have seen some older Android Bug reports about alpha and beta Gradle builds having this issue, but the reports I found were >6 months old (and for previous Gradle versions).
P.S. I know that minifyEnabled = false currently, i have yet to investigate the correct set of proguard rules for my included libraries to prevent the Signed Build from erroring out due to missing libs.
To use shrinkResources you have to use minifyEnabled
As per Android documentation:
Resource shrinking works only in conjunction with code shrinking.
After the code shrinker removes all unused code, the resource shrinker
can identify which resources the app still uses. This is especially
true when you add code libraries that include resources—you must
remove unused library code so the library resources become
unreferenced and, thus, removable by the resource shrinker.
To enable resource shrinking, set the shrinkResources property to true
in your build.gradle file (alongside minifyEnabled for code
shrinking).
use
minifyEnabled false
shrinkResources false
or
minifyEnabled true
shrinkResources true
may be a bug on android gradle plugin
wait google fix bugs
I have an application that has been using multidexing successfully already, but now I have a framework that needs to wrap itself around the main classes.dex, which means it needs to be below the max references limit after wrapping it.
But no matter what Gradle dexOptions I provide, the main classes.dex ALWAYS contains the max 65535 references:
The Gradle settings I have / have tried:
dexOptions { // params tried separately and together
additionalParameters '--minimal-main-dex --set-max-idx-number=55000'
}
defaultConfig {
multiDexEnabled true
// Shouldn't be necessary for source code within module
multiDexKeepFile file('multidex-config.txt')
}
With the multidex-config.txt keep-file containing ONLY my Application class which is pretty small: com/package/name/MyApplication.class
Other info:
minSdkVersion 21
targetSdkVersion 26
Total dex files:
Am I missing something?
EDIT: Okay so I've just figured out that for some reason it works with my 'qa' buildType sometimes, but not on my 'debug' buildType, and also only works on 'qa' on my local machine, not on others including our build server. I run a gradle clean before every build, so it can't be caching that's the problem.
buildTypes {
debug { // DOESN'T WORK
testCoverageEnabled false
zipAlignEnabled false
applicationIdSuffix ".debug"
}
qa { // DOES WORK, sometimes, on my local machine only
initWith(debug)
applicationIdSuffix ".qa"
zipAlignEnabled false
testCoverageEnabled false
}
release { // ALWAYS WORKS - Probably due to minification by Proguard
zipAlignEnabled false // To be signed later
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
Still have no idea why that's happening.
Add following dependency in your gradle file.
dependencies {
...
implementation 'com.android.support:multidex:1.0.2'
}
and also update dexOptions with following code
dexOptions {
javaMaxHeapSize "4g"
}