Android 项目在发布版本中生成过多的 DEX 文件
Android project generating too many DEX files in release builds
我正在开发一款从 Unity 作为 Android Studio 项目导出的游戏。当我 assemble 和 运行 作为调试版本时,它按预期工作。由于我们使用了多个 3rd 方库,有超过 65K 种方法,并且它生成了相当多的 DEX 文件(11 个文件)。查看这些DEX文件的内容,并不都是全的。事实上,它们中的大多数只包含一个 BuildConfig class,或一堆相关的 R classes。事实上,只有 2 个 DEX 文件有任何值得一看的地方,classes7.dex 和 classes11.dex。我什至不知道该应用程序如何 运行s;我认为主要 activity 需要在 classes.dex 中才能工作。但无论如何,一切实际上都很好。
然而,在发布版本中,情况要糟糕得多。我说的是 109(一百零九个!)DEX 文件。由于某种原因,这似乎只是对最初在 11 个 DEX 文件中的 classes 进行了更精细的分离。在这里,事情开始崩溃。启动时,ClassNotFoundExceptions 开始出现在一些 设备上,但在其他设备上它运行良好。我看到的表明它是否有效的共同因素是 OS 版本。所有设备都是运行ning Android OS 5.0+,所以原生支持多索引,但稳定的设备大多是运行ning 6.0+。
主要的 activity 在 classes54.dex 中,它从 classes30.dex 中的一个 class 延伸,从 [=37= 中的一个 class 延伸],从 Activity 延伸而来。不过,它可以找到那些 classes。例如,它抱怨找不到的第一个 class 在 classes91.dex 中结束。
我认为问题出在 gradle 过程中,因为从 Unity 直接导出到 APK 或在 Android Studio 中构建时会出现问题。所以我的问题是我该怎么做:
- 说服Unity/AndroidStudio/Gradle输出合理数量的DEX文件,或者
- 让所有设备查看所有的dex文件,即使有100+,在寻找classes的时候?
从 Unity 导出时创建的当前 build.gradle:
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'
}
}
当minSdkVersion小于21时使用multiDex,应该配置multiDexKeepProguard。
link https://developer.android.com/studio/build/multidex#keep
像这样
...
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
....
如果使用"Run app"按钮生成apk,apk可能包含很多dex文件。
使用"Build->Make Module 'app'"或命令行。
我发现了一个...不是最优的解决方案。我的应用目前的目标是 21+ 作为最小 SDK。如果我将它降低到 20 或更低,构建过程显然会发生变化。只有 2 个 DEX 文件出来。当然这意味着我需要支持 Android 4.4+ 而不是 5.0+。
明确地说,我所做的唯一更改是添加行
minSdkVersion 20
高于 targetSdkVersion 27
,这改变了它的构建方式。如果我将它更改为 minSdkVersion 21
或任何更高的数字,它又会被破坏。
使用Pre-dexing通常是dex文件较多的原因
Pre-dexing 是迭代所有项目模块并将它们从 Java 字节码转换为 Android 字节码的过程。它将每个应用程序模块和每个依赖项构建为单独的 DEX 文件。此 dexOption 用于以增量方式构建并加快构建过程,因为一个模块中的更改只会导致该模块的 dexing。
请尝试在您的 build.gradle 文件中使用以下 dexOptions
android {
...
dexOptions {
preDexLibraries = false
}
}
以上应该可以解决您的问题,您的应用程序不需要支持 Android 4.4。
目标 minSdkVersion 21 时,似乎有 100 个可以读取的 dex 文件的限制:https://android.googlesource.com/platform/art/+/lollipop-release/runtime/dex_file.cc#303。这就是您的应用在将 api 级别降级到 20 后运行良好的原因。
我正在开发一款从 Unity 作为 Android Studio 项目导出的游戏。当我 assemble 和 运行 作为调试版本时,它按预期工作。由于我们使用了多个 3rd 方库,有超过 65K 种方法,并且它生成了相当多的 DEX 文件(11 个文件)。查看这些DEX文件的内容,并不都是全的。事实上,它们中的大多数只包含一个 BuildConfig class,或一堆相关的 R classes。事实上,只有 2 个 DEX 文件有任何值得一看的地方,classes7.dex 和 classes11.dex。我什至不知道该应用程序如何 运行s;我认为主要 activity 需要在 classes.dex 中才能工作。但无论如何,一切实际上都很好。
然而,在发布版本中,情况要糟糕得多。我说的是 109(一百零九个!)DEX 文件。由于某种原因,这似乎只是对最初在 11 个 DEX 文件中的 classes 进行了更精细的分离。在这里,事情开始崩溃。启动时,ClassNotFoundExceptions 开始出现在一些 设备上,但在其他设备上它运行良好。我看到的表明它是否有效的共同因素是 OS 版本。所有设备都是运行ning Android OS 5.0+,所以原生支持多索引,但稳定的设备大多是运行ning 6.0+。
主要的 activity 在 classes54.dex 中,它从 classes30.dex 中的一个 class 延伸,从 [=37= 中的一个 class 延伸],从 Activity 延伸而来。不过,它可以找到那些 classes。例如,它抱怨找不到的第一个 class 在 classes91.dex 中结束。
我认为问题出在 gradle 过程中,因为从 Unity 直接导出到 APK 或在 Android Studio 中构建时会出现问题。所以我的问题是我该怎么做:
- 说服Unity/AndroidStudio/Gradle输出合理数量的DEX文件,或者
- 让所有设备查看所有的dex文件,即使有100+,在寻找classes的时候?
从 Unity 导出时创建的当前 build.gradle:
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'
}
}
当minSdkVersion小于21时使用multiDex,应该配置multiDexKeepProguard。
link https://developer.android.com/studio/build/multidex#keep
像这样
...
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
....
如果使用"Run app"按钮生成apk,apk可能包含很多dex文件。
使用"Build->Make Module 'app'"或命令行。
我发现了一个...不是最优的解决方案。我的应用目前的目标是 21+ 作为最小 SDK。如果我将它降低到 20 或更低,构建过程显然会发生变化。只有 2 个 DEX 文件出来。当然这意味着我需要支持 Android 4.4+ 而不是 5.0+。
明确地说,我所做的唯一更改是添加行
minSdkVersion 20
高于 targetSdkVersion 27
,这改变了它的构建方式。如果我将它更改为 minSdkVersion 21
或任何更高的数字,它又会被破坏。
使用Pre-dexing通常是dex文件较多的原因
Pre-dexing 是迭代所有项目模块并将它们从 Java 字节码转换为 Android 字节码的过程。它将每个应用程序模块和每个依赖项构建为单独的 DEX 文件。此 dexOption 用于以增量方式构建并加快构建过程,因为一个模块中的更改只会导致该模块的 dexing。
请尝试在您的 build.gradle 文件中使用以下 dexOptions
android {
...
dexOptions {
preDexLibraries = false
}
}
以上应该可以解决您的问题,您的应用程序不需要支持 Android 4.4。
目标 minSdkVersion 21 时,似乎有 100 个可以读取的 dex 文件的限制:https://android.googlesource.com/platform/art/+/lollipop-release/runtime/dex_file.cc#303。这就是您的应用在将 api 级别降级到 20 后运行良好的原因。