💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
在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)