这是我的第一篇翻译,英语水平有限,仅供参考。
本篇原文:http://developer.android.com/tools/building/multidex.html
随着Android平台的持续增长,现在已经有巨大数量的Android app。当你的应用或者库所关联的方法数量达到一定的数量后,你就会遇到一个编译错误,这个错误意味着你的app达到了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 is65536.
You may try using --multi-dex option.
产生这些错误的条件里面都包含一个数字:65535,这个数字是代码在一个单个的dex字节码文件中能够被执行的最大方法引用数量。如果你编译了一个App并且遇到了这个错误,那么,恭喜你!你的代码数量看来不少啊。这个文档就是介绍绕过这个限制,并且继续编译你的app。
注意:本文档里提供的方法取代了下面这个Android开发者博客提供的方法Custom Class Loading in Dalvik.
http://android-developers.blogspot.com/2011/07/custom-class-loading-in-dalvik.html
关于65535引用数量限制
APK文件包含的可执行的字节码文件是DalvikExecutable (DEX)的文件形式的,它包含着用来运行你的app的编译过的代码。Dalvik Executable的要求限制在一个单个的dex文件中可以引用的最大方法数量是65535.其中包括Android框架方法,库方法,以及你自己的方法。以前这个限制要求你配置你的app的build进程来生成不仅一个dex文件,就是传说中的multidex configuration.
Multidex支持Android5.0之前的版本
Android5.0之前的版本应用Dalvik环境执行App代码。默认情况下,Dalvik限制App到一个单独的classes,一个apk一个dex字节码文件。为了解决这个限制,你可以用multidex support library(http://developer.android.com/tools/support-library/features.html#multidex),它将成为你的app的主dex文件的一部分,并且管理着去调用一个额外的dex文件和它们的代码
Multidex支持Android5.0以及更高的版本
Android5.0以及更高的版本用一个另外的运行环境叫做ART,它本地已经支持了从Apk文件中加载多个的dex文件。ART在安装应用的时候会去执行预编译,并且浏览classes文件并把它们编译到一个单个oat文件里面。oat文件是能够在Android设备中执行的文件。要看更多的关于Android5.0运行环境的信息请去看https://source.android.com/devices/tech/dalvik/art.html
避免65K限制
将你的app配置为支持高于65k方法数之前,建议采取一下以下的几个步骤来减少你的app里面的方法数量,包括你自己app里面定义的以及你所包含的库里面的方法。下面的几个方法有助于你避免方法数限制:
1.浏览检查下你的项目里面直接或者间接的依赖,保证你的app项目里面所包含的任何一个比较大的库依赖都被用到了,在某种意义上app都依赖了过量的代码。减少你的App的代码依赖一般情况下都可以帮你避免方法数限制
2.利用ProGuard移除掉没用的代码-配置ProGuard设置执行ProGuard来保证你已经缩小了代码量提供发布版本,执行缩减可以保证你不会将没用的代码运行到你的apk里面
利用上面的这些技术能够帮助你避免将你的app配置为支持65k方法数限制。上面几步还可以减少你的apk文件大小,这个对于应用市场来说相当重要,因为对很多应用市场来说带宽越高费用越高
在gradle里配置使你的app支持Multidex
在Android build版本21.1或者更高的版本中的gradle插件支持Multidex作为你的编译配置中的一部分。确保你更新了你的Android SDK build tools版本以及android支持库到最新的版本。
设置你的app项目支持Multidex需要你做一些修改,特别是你需要执行下面的步骤:
1.Change your Gradle build configuration to enable multidex
2.Modify your manifest to reference theMultiDexApplication class
修改你的项目的gradle文件配置,添加一个库支持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 build文件中的defaultConfig, buildType, 或者 productFlavor部分指定为multiDexEnabled
在你的清单文件中添加MultiDexApplication class,从multidex支持库中到应用中
<?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(class.dex)和需要的支持dex(classes2.dex,classes3,dex)。编译系统会把它们打包到Apk文件中来发布
注意:如果你的app用了继承Application的class,你可以重写attachBaseContext()方法,并且调用MultiDex.install(this)来实现multidex,想了解更多的信息,请去看MultiDexApplication文档(http://developer.android.com/reference/android/support/multidex/MultiDexApplication.html)
multidex支持库的局限
multidex支持库有一些已知的限制,也许你应该了解并且去测试一下,当你把它应用到你的build配置中之前。
1.应用在一个设备上开启的时候.dex文件的安装是比较复杂的,如果第二个dex文件比较大的话,甚至有可能导致ANR。这种情况下你应该利用ProGuard的缩减技术来减小你的dex文件大小,并且移除无用代码
2. multidex支持的应用有可能会在早于Android4.0(API14)以前的版本启动不了,由于aDalvik linearAlloc bug (Issue 22586).如果你的目标版本早于14,确保提前测试一下你的应用,也许当应用启动时或者加载一些个别classes时会有一些问题。代码缩减能够减少或者甚至消除这样的潜在问题。
3.使用multidex 支持的应用程序将会造成非常大的内存分配请求,这样的请求也许会造成运行时崩溃,因为Dalvik 虚拟机的linearAlloc限制(它的大小在Android 2.3 之前是 5MB,到了 4.0 后才改成 8MB 或 16MB)。虽然这个限制的大小在4.0以后增加了,但是5.0以前版本的APP还是遇到这个问题。
4.当应用在Dalvik 虚拟机上执行的时候,会有很多复杂的依赖与主dex文件中的类关联。Android编译更新工具来处理这些关联依赖。但是很可能其他包含的库有额外的依赖请求例如内省的使用或本地代码的方法引用。一些只有在multidex 编译工具被更新之后才能用的库就必须要包含到主dex文件中。
优化多dex支持的开发编译过程
一个配置了多dex文件支持的项目很明显的就会增加编译时间。编译系统必须去判断哪些类必须包含到主dex文件,哪些类可以包含到第二个dex文件。这些对于多dex支持的项目来说是例行公事,一般都会耗费更长的时间而且有可能拖慢开发进度。
为了减少编译时间,你应该使用productFlavors
(gradle的Android插件)创建两个类型的输出版本,一个开发版,一个产品版。
对于开发版,定义最小版本的sdk是21.这个设置使用ART-supported format会使编译输出更快。对于发布版,把最小sdk设为你需要的,这个设置生成的apk能够适配更多地版本但是也会耗费更多地时间。
下面的配置例子是说明如何在gradle文件设置那两个版本
android {
productFlavors {
// Define separate dev and prod product flavors.
dev {
// dev utilizes minSDKVersion = 21 to allowthe Android gradle plugin
// to pre-dex each module and produce anAPK that can be tested on
// Android Lollipop without time consumingdex merging processes.
minSdkVersion 21
}
prod {
// The actual minSdkVersion for theapplication.
minSdkVersion 14
}
}
...
buildTypes {
release {
runProguard true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
}
dependencies {
compile 'com.android.support:multidex:1.0.0'
}
当你完成后这些配置,你可以通过使用devDebug
来
转化你的APP, 它能够吧dev模式下的特性和debug模式下的特性组合起来。用这个可以生成一个不使用混淆但是却支持multidex的debug APP,并且最小版本是21。这些设置会对Androidgradle插件产生以下的效果:
1. 编译应用的每一个module到分开的dex文件,这叫预dex
2. 不改变的包含每一个dex文件到apk
3. 最重要的是,这些dex文件不会被组合,并且避免了长时间测算主dex文件需要的内容
最终的结果是快速,增强的编译因为只有修改过的dex文件被计算打包到apk。这样的apk只能在5.0设备上测试。然而,适当的时候配置成你需要的最小版本,加入混淆正常编译再测试也是很有必要的。
你也可以构建其他的变种,加入一个prodDebug
变种构建会增加构建时间,但是可以用于开发之外的测试。prodRelease构建的是最终版。你也可以在命令行执行gradle任务,想了解更多地gradle编译的使用等请去看这里(http://tools.android.com/tech-docs/new-build-system/user-guide)。
提示:你也可以为每个编译类型提供一个定制的清单,或者一个定制的应用类,来使用支持库MultiDexApplication ,或者在需要的时候调用MultiDex.install()。
在Android Studio中使用构建变种版本
构建变种对于多dex支持来说也许会非常有用,Android Studio对构建变种版本提供了可视化操作。
用Android Studio为你的APP构建"devDebug"变种版本需要两步
1.打开构建变种窗口,就在Android Studio左侧下方两个小按钮看下图
2.点击变种版本的名称,选择一个不同的变种版本如下图
注意:只有当你成功synchronized 完项目后这个功能才是可用的。你可以先Tools > Android > Sync Project with Gradle Files
测试多dex支持的APP
当使用自动化测试多dex文件支持的APP时,我们需要在加一些配置。因为多dex支持的APP中的classes代码不在同一个dex文件。工具测试也需要配置为multidex
为了用自动化测试测试多dex的APP,需要配置multidex 支持库中的MultiDexTestRunner
下面是build.gradle中示例代码
android { defaultConfig { ... testInstrumentationRunner "com.android.test.runner.MultiDexTestRunner" } }
注意:假如你的gradle版本低于1.1,你需要加入下面的依赖
dependencies { androidTestCompile('com.android.support:multidex-instrumentation:1.0.1') { exclude group: 'com.android.support', module: 'multidex' } }
为了测试需要,你可以直接使用自动化测试中的runner或者继承它,你也可以在现有的单元测试类里面重写onCreate方法像下面这样:
public void onCreate(Bundle arguments) { MultiDex.install(getTargetContext()); super.onCreate(arguments); ... }
发现有错误之处欢迎指正!