由于Android平台的持续成长,Android apps的大小也一样不断变大。当你的应用程序及其引用的库达到某个大小时,你将遇到一个 表示你的app已经达到了Android app构建架构的一个限制 的build errors。早些时候的构建系统将报出一个类似下面这样的一个error:
Conversion to Dalvik format failed: Unable to execute dex: method ID not in [0, 0xffff]: 65536
更近一些的Android构建系统版本则显示一个不同的error,但指示了相同的问题:
trouble writing output: Too many field references: 131000; max is 65536. You may try using --multi-dex option.
从这两个errors中可以看到一个共同的数字:65,536。这个数字很重要,它表示一个单独的Davlik Executable(dex)字节码文件中的代码可以调用的引用的总个数。如果你在构建一个Android app且遇到了这个error,那么恭喜你,你的代码量非常大!这份文档解释了要如何绕过这个限制并继续构建你的app。
注意:这份文档中提供的指南取代了在Android Developers blog post Custom Class Loading in Dalvik中给出的指南。
Android应用程序(APK)文件以Dalvik Executable(DEX)文件的格式包含了可执行的字节码文件,而DEX包含了用于运行你的app的已编译代码。Dalvik Executable规范限制了一个单独的DEX文件内可被引用的方法的总数为65,536,包括Android framework方法,库方法和你自己的代码中的方法。要绕过这个限制需要你配置你的app的构建过程来产生多个DEX文件,即所谓的multidex配置。
Android 5.0之前版本的平台使用Dalvik runtime来执行app代码。默认情况下,Dalvik限制了apps为每个APK一个单独的classes.dex字节码。为了绕过这个限制,你可以使用multidex support library,它们是你的app的主DEX文件的一部分,来管理对于它们额外包含的代码和DEX文件的访问。
Android 5.0及更高的版本使用了一个称为ART的runtime,它原生支持由应用程序APK文件加载多个dex文件。ART在应用程序安装时执行预编译,则安装时会扫描classes(..N).dex文件并把它们编译为一个单独.oat文件给Android设备执行。关于Android 5.0 runtime的更多信息,请参考Introducing ART。
在配置你的app以启用对 65K或更多方法引用 的使用之前,你应该执行一些步骤来减少你的app代码调用的引用的总个数,包括你的app代码定义的方法及库。下面的做法可以帮你避免打到dex引用限制:
使用这些技术可以帮你避免对构建配置做改动,同时能够在你的app中引用更多的方法。这些步骤也能减小你的APKs的大小,这一点对那些带宽成本很高的市场特别重要。
Android SDK Build Tools 21.1及更高版本中的Gradle Android插件支持把multidex作为你的构建配置的一部分。在试着配置你的app支持multidex之前,请确认你已经使用SDK Manager把Android SDK Build Tools工具和Android Support Repository更新到了最新版。
要设置你的app开发工程使用multidex配置,需要你对你的app开发工程做一些修改。特别地你需要执行下面的这些步骤:
修改你的app Gradle构建文件配置包含support library并启用multidex输出,如下面的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' }
注意:你可以在Gradle构建文件的defaultConfig,buildType,或productFlavor段中指定multiDexEnabled设定。
在你的manifest中向你的application元素添加来自于multidex support library的MultiDexApplication类。
<?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>
给app添加了这些配置设定之后,Android构建工具会构建一个主dex(classes.dex),并在需要时构建一些supporting(classes2.dex,classes3.dex)。构建系统将把它们打包进一个APK以用于发布。
注意:如果你的app继承了Application类,你可以覆写attachBaseContext()方法并调用MultiDex.install(this)以启用multidex。要了解更多信息,请查看MultiDexApplication参考文档。
multidex support library有一些已知的限制,你应该对它们有所意识,并在把它合进你的app构建配置时做一些针对行的测试:
由于构建系统必须做 关于什么类必须被放在主DEX文件而什么类可以被放入次要DEX文件 的复杂决定,一个multidex配置请求 可能会使构建过程的时间大为增加。这意味着作为开发过程一部分而执行的构建例程,在具有multidex时,将消耗更多时间,并可能降低开发过程的速度。
为了延缓multidex输出造成的构建时间增长,你应该使用Gradle Android插件的productFlavors,在你的构建输出上创建两个variantions:一个开发flavor和一个产品flavor。
对于开发flavor,设置最小SDK版本为21。这种设定将使用ART支持的格式产生multidex输出,这要快得多。对于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 variant,它结合了dev productFlavor和debug buildType的属性。使用这个target将创建一个debug app,其中禁掉了proguard,启用了multidex,minSdkVersion被设置为了Android API level 21。这些设定将使得Android gradle插件做下面的事情:
由于只需要对修改了的模块的dex文件进行重新计算并打包进APK,这些设定导致了快速的,增量构建。这些构建的结果APK只能用于Android 5.0设备上的测试。然而,通过将配置实现为一个flavor,你将保有为release执行普通的构建的能力 - 适当的最小SDK level及proguard设定。
你也可以构建其它的variants,包括prodDebug variant build,它需要花费更多的时间来构建,但可被用于开发之外的测试。在展示的配置中,prodRelease variant将是最终的测试和发布版。如果你在通过命令行执行gradle tasks,你可以使用在最后附加了DevDebug的标准命令 (比如./gradlew installDevDebug)。更多关于使用Gradle tasks的flavors的信息,请查看Gradle Plugin User Guide。
提示:你也可以为每个flavor提供一个定制的manifest,或一个定制的application,这使你可以使用support library MultiDexApplication类,或只在需要的variant中调用MultiDex.install() 。
当使用multidex时,对于管理构建过程来说,build variants可能非常有用。Android Studio允许你在UI中选择这些build variants。
让Android Studio构建你的app的"devDebug" variant:
图1. Android Studio中左边面板显示一个build variant的截图。
注意:打开这个窗口的选项只有在你已经使用命令Tools > Android > Sync Project with Gradle Files成功地同步了Android Studio和你的Gradle文件之后才可用。
当使用instrumentation测试multidex apps时,需要一些额外的配置来启用test instrumentation。由于在multidex apps中,类的代码的位置不是在一个单独的DEX文件中的,则除非针对multidex做了配置,否则instrumentation tests无法适当地运行。
要用instrumentation tests测试一个multidex app,则配置multidex testing support library的MultiDexTestRunner。下面的示例build.gradle文件演示了如何配置你的构建来使用这个test runner:
android { defaultConfig { ... testInstrumentationRunner "com.android.test.runner.MultiDexTestRunner" } }
注意:使用Gradle版本低于1.1的Android Plugin时,你需要为multidex-instrumentation添加下面的依赖:
dependencies { androidTestCompile('com.android.support:multidex-instrumentation:1.0.1') { exclude group: 'com.android.support', module: 'multidex' } }
你可能直接使用instrumentation test runner类或继承它来适应你的测试需要。或者你可以像下面这样在现有的instrumentations中覆写onCreate:
public void onCreate(Bundle arguments) { MultiDex.install(getTargetContext()); super.onCreate(arguments); ... }
注意:现在还不支持使用multidex来创建一个测试APK。
Done。
原文。