##問題引入##
隨著項目工程越來越龐大,代碼的方法數不斷增長到一定程度,就出現Android 低版本系統應用無法安裝的情況。那麼這是哪裡出錯了?Android系統對安裝包有哪些限制?
前一陣子,我們發現公司的某一個業務,在Android 2.3及系統安裝不了。此時,我們該業務的Android客戶端開發已經有50個人。一般外面公司的Android開發也就2~3個,代碼的體量也很難增長到像我們這樣的規模。但任何一個大項目都會碰到該問題。
我們來看一下,出現問題時,開發工具會有如下的提示:
> Conversion to Dalvik format failed: Unable to execute dex: method ID
> notin[0,0xffff]:65536
或是
> trouble writing output: Too many field references:131000; max is65536.
> You may tryusing--multi-dex option.
上述兩個錯誤提示不一樣,但問題都是同一個問題。只是開發工具的編譯系統版本不同,提示不同。可以看出,是某一項達到系統規定的閥值,導致安裝失敗了。
**問題定位**
從上述的提示來看,裡面有一個數字65536很搶眼,這個正好是Java int的最大值。由於Android代碼是以dex形式的存在,那麼dex在系統裡是如何定義的呢?
首先,每一個Dex文件,都會有這麼一個頭部信息DexHeader:
從上面可以看出,在頭部信息裡面分別定義了filed(字段)、method(方法)、classDefs(類)等的數量,這個數量是u4類型。至於u4類型的定義是
可以從上面的定義看出,u4類型就是uint32_t,而uint32_t正好是無符號整型2^32=65536。因此,這也就不難解釋,為什麼dex文件超過這個限制後,就出現無法安裝的情況。
一般情況下,dex的方法數最多,因此很容易達到65536這個數量限制,而字段和類則比較難。所以,出現問題的都是方法超出居多。
##Dex引用構成##
一個Dex文件,它的引用組要由下面三部分組成。
因此,dex引用的方法數不僅僅和自己編寫的代碼有關,還和引用系統的方法數、第三方集成庫的方法數有關。因此,方法數超標,也是多方面的原因構成的。
想看自己dex文件的方法數,可以搜索dex-method-counts獲得自己dex數量。
##解決思路##
從上面的分析可以看出,出問題的原因是因為單個Dex引用的方法數超標了。那麼我們是否可以拆分多個dex,從而避免單個dex的方法數超標?答案當然是可以的。
目前,為了解決dex方法數超標的問題,有三種主流思路:代碼瘦身、插件化和分dex。這三種解決方案可以結合使用,效果更好。
####代碼瘦身
代碼瘦身,可以從自己定義/引用的方法和第三方引入的庫下手。
通過review自己的代碼,減少方法數的定義(去掉一行函數,比如get和set,一般都是一行的,可以直接對變量做引用)、合併方法、減少對外部的依賴,從而減少最終dex引用的限制。
第三方引用庫,一般一個引用jar包裡面,有很多方法其實我們是引用不到的,我們可以對這些jar包做瘦身(jar是壓縮包,裡面都是class文件,我們去掉一些不相關的class,從而減少jar包大小),從而減少最終dex的引用限制。
同時,可以引入Proguard工具,開啟Proguard的代碼瘦身功能,會自動幫你刪除無用的代碼,最終生成的dex文件也會變小的。
####插件化
插件化,也就是對程序中獨立的模塊做成插件,從而減少主dex文件的大小。插件化的思路其實就是對dex做拆分,從而使主dex變得更小,插件則是以獨立的dex存在。
但插件化需要自己開發一套插件化的框架,成本較高,而且只有獨立的模塊才適合做插件。很多時候,我們的代碼存在很多引用,很難拆成獨立的模塊。
因此,插件化,是無法從根本上解決這個問題。
####分Dex
上述方案都不能從根本上解決問題,可以採用分dex的方案,適用範圍更廣。
分dex是將dex分成多個dex,從而避免單個dex的引用超過限制,分dex的方案不需要關心獨立性問題,而且Android Studio開發工具已經支持這項能力,使用起來,成本也很低。
#####分Dex實現
Gradle的Android插件,從SDK build tools 21.1或是更高的版本就支持多dex的能力,需要自己手動配置一下。步驟如下:
5.1開啟分包功能
在build.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'
}
```
2修改Application入口
一種方案是,直接修改AndroidManifest,使用MultiDexApplication,如下所示:
```
<manifest>
package="com.example.multidex">
<application>
...
android:name="android.support.multidex.MultiDexApplication">
...
/<application>/<manifest>
```
另一種方案, 如果自己有自定義的Application,則可以重寫attachBaseContext()方法,在裡面調用MultiDex.install(this) 來開啟分dex功能。
當所有的配置OK後,Android編譯工具會自動生成一個主dex(classes.dex),和其它附屬dex(classes2.dex,classes3.dex)。當然這個附屬dex是根據需要來會生成。
#####分Dex侷限性
雖然分Dex,可以解決dex方法數限制問題,但開發必須關注以下問題:
* 1附屬包過大
附屬包如果過大,可能導致應用啟動時發生ANR。所以,需要使用Proguard來做代碼瘦身,減少附屬包的大小。
* 2低版本運行問題
在Android 4.0以下(API level 14以下),可能出現運行不起來的問題,原因是Dalvik linearAlloc的bug。同樣通過proguard可以瘦身代碼,避免這個問題。
* 3內存消耗問題
使用多dex方案,會導致應用請求更多的內存空間,從而出現crash。原因同樣來自Dalvik的linearAlloc的內存分配限制。雖然在Android 4.0上已經提高了內存分配限制,但仍然還是很有可能達到這個限制。
* 4代碼分包的複雜性
雖然,分包解決了dex引用限制的問題,但是由於dex內部複雜的引用,所以,在對代碼分包時,必須考慮到啟動時就需要用到的,都必須放到主dex中。
目前編譯開發工具還不支持指定class必須放到主dex中,後續開發工具會逐步完善並支持該功能。當然,如果你覺得有必要控制哪些代碼必須在主dex裡面,可以自己編寫編譯腳本。
閱讀更多 北漂程序員大鬆 的文章