在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 的應用](http://www.hmoore.net/alex_wsc/android_plugin/481528)。但是有時候官網的介紹,當你按照介紹一步一步執行時,發現結果并不理想,很可能是自己的電腦環境和官網介紹時的環境不一樣,所以,遇到問題,慢慢解決。
當我們打開工程所在的目錄文件夾下`MultiDexTest\build\outputs\apk\debug`找到生成的apk文件,解壓可以將其中的dex文件提取出來。如下圖所示

可以看到圖中有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的動態加載技術