在Android 中单个dex 文件所能够包含的最大方法数为65536 ,这包含AndroidFrame Work、依赖的jar 包以及应用本身的代码中的所有方法。65536 是一个很大的数,一般来说一个简单应用的方法数的确很难达到65536,但是对于一些比较大型的应用来说,65536 就很容易达到了。当用的方法数达到65536 后,编译器就无法完成编译工作并抛出类似下面的异常:
~~~
UNEXPECTED TOP-LEVEL EXCEPTION:
java.lang.IllegalArgumentException: method ID not in [0, 0xffff]: 65536
at com.android.dx.merge.DexMerger$6.updateIndex(DexMerger.java:501)
at com.android.dx.merge.DexMerger$IdMerger.mergeSorted(DexMerger.java:282)
at com.android.dx.merge.DexMerger.mergeMethodIds(DexMerger.java:490)
at com.android.dx.merge.DexMerger.mergeDexes(DexMerger.java:167)
at com.android.dx.merge.DexMerger.merge(DexMerger.java:188)
at com.android.dx.command.dexer.Main.mergeLibraryDexBuffers(Main.java:439)
at com.android.dx.command.dexer.Main.runMonoDex(Main.java:287)
at com.android.dx.command.dexer.Main.run(Main.java:230)
at com.android.dx.command.dexer.Main.main(Main.java:199)
at com.android.dx.command.Main.main(Main.java:103)
~~~
另外一种情况有所不同,有时候方法数并没有达到65536 ,并且编译器也正常地完成了编译工作,但是应用在低版本手机安装时异常中止,异常信息如下:
~~~
E/dalvikvm: Optimization failed
E/installd : dexopt failed on '/data/dalvik-cache/data@app@com.ryg.
multidextest-2.apk@classes.dex' res = 65280
~~~
为什么会山现这种悄况呢?其实是这样的, dexopt 是一个程序,应用在去装时,系统会通过dexopt 来优化dex 文件,在优化过程中dexopt 采用一个固定大小的缓冲区来存储应用中所有方法的信息,这个缓冲区就是LinearAlloc. LinearAlloc 缓冲区在新版本的Android系统中其大小是8MB 或者16MB ,但是在Android 2.2 和2.3 中却只有5MB ,当待安装的apk 中的方法数比较多时,尽管它还没有达到65536 这个上限,但是它的存储空间仍然有可能超出5MB ,这种情况下 dexopt 程序就会报错,从而导致安装失败,这种情况主要在2.x 系列的手机上出现。
可以看到,不管是编译时方法数越界还是安装时的dexopt 错误,它们都给开发过程带来了很大的困扰。
如何解决方法数越界的问题呢?我们首先想到的肯定是删除无用的代码和第三方库。没错,这的确是必须要做的工作, 但是很多情况下即使删除了无用的代码,方法数仍然越界,这个时候该怎么办呢?针对这个问题,之前很多应用都会考虑采用插件化的机制来动态加载部分dex,通过将一个dex 拆分成两个或多个dex ,这就在一定程度上解决了方法数越界的问题。但插件化是一套重量级的技术方案,并且其兼容性问题往往较多,从单纯解决方法数越界的角度来说,插件化并不是一个非常适合的方案,关于插件化的意义将在下一 节中进行介绍。为了解决这个问题, Google 在2014 年提出了Multidex 的解决方案,通过multidex 可以很好地解决方法数越界的问题,并且使用起来非常简单。
关于如何使用[Multidex](https://developer.android.com/reference/android/support/multidex/MultiDex.html#install(android.content.Context)),官网给出了详细的介绍,可参考翻译文章[Google官网——配置方法数超过 64K 的应用](https://www.kancloud.cn/alex_wsc/android_plugin/481528)。但是有时候官网的介绍,当你按照介绍一步一步执行时,发现结果并不理想,很可能是自己的电脑环境和官网介绍时的环境不一样,所以,遇到问题,慢慢解决。
当我们打开工程所在的目录文件夹下`MultiDexTest\build\outputs\apk\debug`找到生成的apk文件,解压可以将其中的dex文件提取出来。如下图所示
![](https://box.kancloud.cn/d6e2bc9100a07400ff4c5451caeda16a_722x250.png)
可以看到图中有3个dex文件,一个主dex文件,两个副的dex文件。
可以通过build.gradle 文件中一些其他配置项来定制dex 文件的生成过程。在有些情况下,可能需要指定主dex 文件中所要包含的类,这个时候就可以通过`--main-dex-list `选项来实现这个功能。下面是修改后的build.gradle 文件,在里面添加了afterEvaluate 区域,在afterEvaluate 区域内部采用了`--main-dex-list` 选项来指定主dex 中要包含的类,如下所示。
~~~
afterEvaluate {
println "afterEvaluate"
tasks.matching {
it.name.startsWith('dex')
}.each { dx ->
def listFile = project.rootDir.absolutePath + '/app/maindexlist.txt'
println "root dir:" + project.rootDir.absolutePath
println "dex task found: " + dx.name
if (dx.additionalParameters == null) {
dx.additionalParameters = []
}
dx.additionalParameters += '--multi-dex'
dx.additionalParameters += '--main-dex-list=' + listFile
dx.additionalParameters += '--minimal-main-dex'
}
}
~~~
在上面的配置文件中, `--multi-dex `表示当方法数越界时则生成多个dex 文件,`--main-dex-list` 指定了要在主dex 中打包的类的列表, `--minimal-main-dex` 表明只有`--main-dex-list `所指定的类才能打包到主dex 中。它的输入是一个文件,在上面的配置中,它的输入是工程中app 目录下的**maindexlist.txt** 这个文件,在**maindexlist.txt**中则指定了一系列的类,所有在**maindexlist.txt**中的类都会被打包到主dex 中。注意**maindexlist.txt**这个文件名是可以修改的,但是它的内容必须要遵守一定的格式,下面是一个示例,这种格式是固定的。
~~~
com/ryg/multidextest/TestApplication.class
com/ryg/multidextest/MainActivity.class
// multidex
android/support/multidex/MultiDex.class
android/support/multidex/MultiDexApplication.class
android/support/multidex/MultiDexExtractor.class
android/support/multidex/MultiDexExtractor$1.class
android/support/multidex/MultiDex$V4.class
android/support/multidex/MultiDex$V14.class
android/support/multidex/MultiDex$V19.class
android/support/multidex/ZipUtil.class
android/support/multidex/ZipUtil$CentralDirectory.class
~~~
程序编译后可以反编译apk 中生成的主dex 文件,可以发现主dex 文件的确只有**maindexlist.txt** 文件中所声明的类,读者可以自行尝试。**maindexlist.txt** 这个文件很多时候都是可以通过脚本来自动生成内容的,这个脚本语要根据当前的项目向行实现,如果不采用脚本,人工编辑**maindexlist.txt** 也是可以的。
>[info] **注意**:目前官网已经提出使用[multiDexKeepFile](http://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.BuildType.html#com.android.build.gradle.internal.dsl.BuildType:multiDexKeepFile) 或 [multiDexKeepProguard](http://google.github.io/android-gradle-dsl/2.2/com.android.build.gradle.internal.dsl.BuildType.html#com.android.build.gradle.internal.dsl.BuildType:multiDexKeepProguard) 属性声明它们,但是可能是由于编译环境的不同,并没有成功,而且按照作者上面的配置也没有成功,甚至将作者的源码整个project也没有成功,很可能也是由于环境的不同(比如Android studio版本的升级,gradle版本升级造成的配置方法不一样),总之上面的应该没有错
需要注意的是, multidex 的jar 包中的9 个类必须也要打包到主dex 中, 否则程序运行时会抛出异常,告知无法找到multidex 相关的类。这是因为Application 对象被创建以后会在attacbBaseContext 方法中通过MultiDex.install(this)来加载其他dex 文件,这个时候如果MultiDex 相关的类不在主dex 中,很显然这些类是无法被加载的,那么程序执行就会出错。同时由于Application 的成员和代码块会先于attachBaseContext 方法而初始化,而这个时候其他dex 文件还没有被加载,因此不能在Application 的成员以及代码块中访问其他dex 中的类,否则程序也会因为无法加载对应的类而中止执行。
Multidex 方法虽然很好地解决了方法数越界这个问题,但它也是有一些局限性的,下面是采用multidex 可能带来的问题:
* (1)应用启动速度会降低。由于应用启动时会加载额外的dex 文件,这将导致应用的启动速度降低,甚至时能出现ANR 现象,尤其娃其他dex 文件较大的时候,肉此要避免生成较大的dex 文件。
* (2)由于Dalvik linearAlloc 的bug,这可能导致使用multidex 的应用无法在Android 4.0以前的手机上运行,因此市要做大量的兼容性测试。同时由于Dalvik linearAlloc 的bug,有可能出现应用在运行中由于采用了multidex 方案从而产生大量的内在消耗的情况,这会导致应用崩溃。
在实际的项目中, (1)中的现象是客观存在的,但是(2)中的现象日前极少遇到,综合来说, multidex 还是一个解决方法数越界非常好的方案, t可以在实际项目中使用。
总之,在使用Multidex的时候可能还会遇到更多的坑。
具体可以参考以下文章
[Android MultiDex实践:如何绕过那些坑?](http://mp.weixin.qq.com/s/N5SCrPX8pf1vi7B_OcRRTg)
- 前言
- 第一章Activity的生命周期和启动模式
- 1.1 Activity生命周期全面分析
- 1.2 Activity的启动模式
- 1.3 IntentFilter的匹配规则
- 第二章IPC
- 转 chapter IPC
- 转IPC1
- 转IPC2
- Binder讲解
- binder
- Messenger
- 一、Android IPC简介
- 二、Android中的多进程模式
- 三、IPC基础概念介绍
- 四、Android中的IPC方式
- 五、Binder连接池
- 第三章
- 第九章四大组件的工作过程
- 第十章
- 第13章 综合技术
- 使用CrashHandler 来获取应用的crash 信息
- 使用Multidex来解决方法数越界
- Android的动态加载技术