本文翻译自http://developer.android.com/intl/zh-cn/tools/building/multidex.html#about。主要介绍当我们Android App中函数超过65536时构建失败的原因及解决办法!
-------------------------分割线--------------------------------------------------
随着android platform的持续增长,android apps的大小也在增长。当你的应用程序包括其所引用的库达到一定的规模,你将遇到构建错误,这表明你的程序已经达到到了android 应用框架的限制。在早期的构建系统会报告如下错误信息:
Conversion to Dalvik format failed:
Unable to execute dex: method ID not in [0, 0xffff]: 65536
最近版本的构建系统会显示不同的错误,这是同一个问题的提示信息:
trouble writing output: Too many field references: 131000; max is 65536. You may try using --multi-dex option.
这两种错误情况显示了一个共同的数字:65536。这个数字代表的是单个Dalvik可执行的(DEX)字节码文件的可以调用方法总数。如果你已经创建了android应用,并且收到了这个错误,那么恭喜你,你的代码太多了!本文档将介绍如何突破这个限制,继续构建你的app。
APK文件包含用于运行你的应用程序编译代码形式的可执行字节码文件(Dalvik Executable DEX)。Dalvik可执行的规范限制了在一个单一的DEX文件中引用到包括Android框架方法,库方法等方法的总数为65,536。想要突破此限制,您需要配置您的应用程序的构建过程,生成多个DEX文件,被称为multidex配置。
Android5.0之前的版本使用的Dalvik运行时执行应用程序代码。默认情况下,Dalvik的限制的应用程序,每APK一个classes.dex字节码文件。为了解决这个限制,可以使用multidex support library,成为您的应用程序的主DEX文件的一部分,然后设法获得了额外的DEX文件和它们所包含的代码。
Android5.0以及更高版本使用的是ART runtime,可以支持原生从APK文件中加载多个dex文件。ART进行预编译的应用程序安装时它会扫描类(.. N).dex文件,并通过Android设备编译成一个单一的.oat文件执行。对于在Android5.0运行时的详细信息,请参考 Introducing ART。
在配置您的应用程序能够使用65K+方法个数之前,您应该采取措施来减少您的应用程序代码调用引用的总数,包括您的应用程序代码中的方法和库定义的方法。以下策略可以帮助您避免超过DEX参考限值:
看您的应用程序的直接和间接性依赖:确保在你的app中包含的library的用途要和其代码量匹配。一个比较常见的反例就是包含巨大的代码量,用途却很小。减少你的app的library依赖往往可以帮你避免dex reference限制。
删除未使用的代码混淆(code with ProGuard):配置可以运行你的app的ProGuard并确保可以在正式构建程序的时候为你的程序瘦身。
使用这些技术可以帮助你避免更改程序构建配置时需要启用更多的方法引用。对于宽带成本很高的市场来说,这些步骤很重要。
Android插件Gradle 可Android SDK Build Tools 21.1和更高版本中支持multidex作为构建配置的一部分。在为你app配置multidex之前,请先使用SDK Manager将Android SDKBuild Tools 和Android Support Repository更新到最新版本。
在你的app中使用multidex配置之前,需要对你的程序开发做一些相应修改。你需要执行以下步骤:
1.修改你的 Gradle构建配置以启用multidex
2.修改你的AndroidManifest 去引用MultiDexApplication class。
修改build.gradle配置去引用support library和启用multidex输出,如以下所示build.gradle片段
android { compileSdkVersion 21 buildToolsVersion "21.1.0" defaultConfig { ... minSdkVersion 14 targetSdkVersion 21 ... // Enabling multidex support. multiDexEnabled true } ... } dependencies { compile 'com.android.support:multidex:1.0.0' }
Note:你可以在build.gradle文件的defaultConfig,buildType,或者productFlavor中设置multiDexEnable。
在你的AndroidManifest的application元素中添加MultiDexApplication class:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.android.multidex.myapplication"> <application ... android:name="android.support.multidex.MultiDexApplication"> ... </application> </manifest>
当这些配置添加到一个应用程序中,Android构建工具会构建一个dex(classes.dex),根据需要会继续构建(classes2.dex, classes3.dex)。然后构建系统将他们打包进同一个apk中。
Note: 如果你的app使用了自己定义的Application class,你可以重写attachBaseContext()方法并在其中调用MultiDex.install(this)去实现multidex。更多信息请参考MultiDexApplication
你应该意识到multidex support library有一些已知的限制,所以当你在将他合并到你的应用程序中时需要测试一下:
1..dex文件的安装在启动设备的数据分区很复杂,如果二级dex文件太大可能导致程序没有响应(ANR)。在这种情况下,您应与混淆器(ProGuard)应用代码缩减技术,减少.dex文件的大小和删除未使用的部分代码。
2.程序不能在在早已Android4.0(API level 14)之前的版本上使用multidex由于a Dalvik linearAlloc bug(Issue 22586)(http://b.android.com/22586)如果你使用API Level 14之前的版本,确保执行您的应用程序在启动时或者装载特定的类时,测试你的程序是否会出现问题。代码缩减可以消除这些潜在问题。
3.应用程序使用multidex配置会发出非常大的内存分配请求,这可能会导致运行时崩溃,由于a Dalvik linearAlloc bug(Issue 78035)(http://b.android.com/78035)。
4.Dalvik runtime执行时the primary dex文件可能需要复杂的请求。Android build tooling 更新处理Android需求,但是其他included libraries可能有额外的依赖需要,包括调用本地的java代码。一些library可能无法使用,直到multidex build tools更新后允许您指定必须包含在主dex文件的类。
multidex配置需要显著增加构建处理时间,因为构建系统必须做出复杂决定,来确定哪些类必须包含在主dex文件以及哪些类可以包含在第二级dex文件中。这意味着常规构建执行与multidex作为开发过程的一部分,通常需要更长的时间,这可能减缓你的程序开发进度。
为了减少multidex输出构建时间,你应该创建两个变量来使用Android构建输出插件Gradle productFlavors: a development flavor and a production flavor。
a development flavor,设定一个最低21的SDK版本。这个设置生成multidex输出比使用ART-supported格式要快得多。the release flavor,设定一个最低SDK版本匹配你的项目最低支持版本。这个设置生成一个multidex APK,与更多的设备兼容,但需要更长的时间来构建。
以下构建配置示例演示了如何在Gradle构建文件设置这些 flavors :
android {
productFlavors {
// Define separate dev and prod product flavors.
dev {
// dev utilizes minSDKVersion = 21 to allow the Android gradle plugin
// to pre-dex each module and produce an APK that can be tested on
// Android Lollipop without time consuming dex merging processes.
minSdkVersion 21
}
prod {
// The actual minSdkVersion for the application.
minSdkVersion 14
}
}
...
buildTypes {
release {
runProguard true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
}
dependencies {
compile 'com.android.support:multidex:1.0.0'
}
完成了这个配置更改后,您可以使用与app的devDebug变量相结合的属性dev productFlavor和debug buildType。利用这个去创建一个minSdkVersion为Android API Level 21不使用proguard,启用multidex的debug app 。这些设置使Android gradle插件执行以下操作:
1.构建应用程序的每个模块(包括依赖库)作为单独的dex文件。这通常被称为pre-dexing。
2.包括每个dex文件没有修改APK文件
3.最重要的是,dex的模块文件不会被结合,所以可以避免来确定主dex文件内容的长时间运行的计算。
这些设置导致快速、增量构建,因为只有dex文件修改模块的会重新计算,重新包装成APK文件。这些设置构建APK只能用于测试Android 5.0及以上设备。然而,通过实现配置的flavors,你能够执行正常构建release-appropriate最低SDK和proguard设置
还可以建立其他变量,包括构建prodDebug变量,这需要更长的时间来构建,但可以用于测试外的开发。如果你从命令行执行gradle任务,您可以使用标准命令在结束的位置附加DevDebug(如./ gradlew installDevDebug)。
关于使用flavors与Gradle任务的更多信息,参考Gradle Plugin User Guide(http://tools.android.com/tech-docs/new-build-system/user-guide)
Tip:您也可以提供一个自定义清单,或者为每个flavor自定义一个应用程序类,允许你在变量需要的时候使用MultiDexApplication应用程序类库或者调用 MultiDex.install()。
Build variants对管理multidex构建过程是非常有用的,Android Studio可以使用可视化操作来选择这些变量。
1.打开位于 left-sidebar的Build Variants 窗口(一般是在Android studio的左下)
2.点击Build Variants的名称来选择不同的变量,,如图1所示
当multidex应用程序中使用instrumentation tests时,需要进行额外的配置。因为代码在multidex应用程序中不是位于单一DEX文件。所以instrumentation tests不能正常运行,除非程序为multidex配置。
为了使用instrumentation tests测试multidex app,需要配置MultiDexTestRunner。
下面build.gradle文件演示了如何配置来使用这个测试运行器:
android {
defaultConfig {
...
testInstrumentationRunner "com.android.test.runner.MultiDexTestRunner"
}
}
Note: With Android Plugin for Gradle versions lower than 1.1, you need to add the following dependency for multidex-instrumentation:
dependencies {
androidTestCompile('com.android.support:multidex-instrumentation:1.0.1') {
exclude group: 'com.android.support', module: 'multidex'
}
}
你可以用instrumentation tests runner class直接或扩展它来适应您的测试需求。或者,您可以在现有的instrumentation中覆盖onCreate:
public void onCreate(Bundle arguments) {
MultiDex.install(getTargetContext());
super.onCreate(arguments);
...
}
Note: Use of multidex for creating a test APK is not currently supported.