4
votes

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:

  1. convince Unity/Android Studio/Gradle to output a sensible number of DEX files, or
  2. 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'
    }

}
3

3 Answers

0
votes

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.

0
votes

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.

0
votes

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.