Android 多Dex分包機制

##問題引入##

隨著項目工程越來越龐大,代碼的方法數不斷增長到一定程度,就出現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裡面,可以自己編寫編譯腳本。


分享到:


相關文章: