Multidex記錄二:缺陷&解決
為什麼要用記錄呢,因為我從開始接觸Android時我們的專案就在65535的邊緣。不久Google就出了multidex的解決方案。我們也已經接入multidex好多年,但我自己還沒有接入,所以本博文只是作者自己對multidex接入中產生的問題以及解決方案做理解和記錄。
Multidex的缺陷
ofollow,noindex">Multidex介紹和使用 中已經說了一部分 multidex
的侷限性:
- 1、在冷啟動時因為需要安裝DEX檔案,如果DEX檔案過大時,處理時間過長,很容易引發ANR(Application Not Responding);
- 2、採用MultiDex方案的應用可能不能在低於Android 4.0 (API level 14) 機器上啟動,這個主要是因為Dalvik linearAlloc的一個bug(問題 22586 ) ;
- 3、採用MultiDex方案的應用因為需要申請一個很大的記憶體,在執行時可能導致程式的崩潰,這個主要是因為Dalvik linearAlloc 的一個限制問題 78035 ),這個限制在 Android 4.0 (API level 14)已經增加了, 應用也有可能在低於 Android 5.0 (API level 21)版本的機器上觸發這個限制。
Google官方給解決辦法就是混淆、混淆~!~!
Dalvik LinearAlloc
侷限 2和3
都與 Dalvik LinearAlloc
,我們先來看一下 Dalvik LinearAlloc
是什麼:
線性記憶體分配器LinearAlloc的目的在於簡單、快速地分配只寫一次(write-once)的記憶體(即分配並完成初始化寫入後一般不會再改變,保持只讀性質)
LinearAlloc它主要用來管理Dalvik中類載入時的記憶體,因為類載入後通常是隻讀屬性,而不需要去改變且在程式的整個執行週期都是有效的,同時它還有共享的特性,一個應用載入後其它程序可以共享使用這些已載入的類從而加快程式的啟動和執行速度。
在Android版本不同分別經歷了 4M/5M/8M/16M
限制,目前主流4.2.x系統上可能都已到16M, 在 Gingerbread
或者以下系統LinearAllocHdr分配空間只有5M大小的, 高於 Gingerbread
的系統提升到了8M。Dalvik linearAlloc是一個固定大小的緩衝區。在應用的安裝過程中,系統會執行一個名為dexopt的程式為該應用在當前機型中執行做準備。dexopt使用LinearAlloc來儲存應用的方法資訊。Android 2.2和2.3的緩衝區只有5MB,Android 4.x提高到了8MB或16MB。當方法數量過多導致超出緩衝區大小時,會造成dexopt崩潰。
LinearAlloc解決方法
這個問題實質上是dex過大的問題,因為我們使用的 multidex
,dx命令就已經支援:–multi-dex 引數來直接自動分包。
我們檢視 dx
命令:

multidex相關引數說明:
- –multi-dex:多 dex 打包的開關
- –main-dex-list=
:引數是一個類列表的檔案,在該檔案中的類會被打包在第一個 dex 中 - –minimal-main-dex:只有在–main-dex-list 檔案中指定的類被打包在第一個 dex,其餘的都在第二個 dex 檔案中。
發現並沒有控制 dex
中方法數的引數,那麼繼續檢視 dx
的原始碼,我們找到一個 maxNumberOfIdxPerDex
變數用來指定 dex
的最大方法數。
//65536 private int maxNumberOfIdxPerDex = DexFormat.MAX_MEMBER_IDX + 1;
同時又一個隱藏的 --set-max-idx-number
引數可以用來修改 maxNumberOfIdxPerDex
的值:

我們修改專案的 build.gradle
指令碼:
android.applicationVariants.all { variant -> dex.doFirst{ dex-> if (dex.additionalParameters == null) { dex.additionalParameters = [] } dex.additionalParameters += '--set-max-idx-number=48000' } }
--set-max-idx-number=
用於控制每一個dex的最大方法個數,寫小一點可以產生好幾個dex。為了避免2.3機型runtime 的linearAlloclimit ,最好保持每一個dex體積<4M ,剛才的的 value<=48000
。
Application Not Responding解決:
Multidex
的安裝是比較耗時的,所以如果放在主執行緒中就會產生ANR。
目前有兩類解決辦法:
放在非同步執行緒;
放在其他程序(我們使用的是第二種,下邊詳細講解);
非同步執行緒執`MultiDex.install
最有名的是美團的方案:精簡主dex+非同步載入secondary.dex 。對非同步化執行速度的不確定性,他們的解決方案是重寫Instrumentation execStartActivity 方法,hook跳轉Activity的總入口做判斷,如果當前secondary.dex 還沒有載入完成,就彈一個loading Activity等待載入完成,如果已經載入完成那最好不過了。
侷限性:第一個dex必須包含所有可能啟動之後ClassLoader的類,不然一定會產生 NoClassDefFoundError
異常。Application的啟動入口有多重,點選桌面icon只不過是其中的一種,而且有些時候啟動Application不一定會開啟Activity。
放在其他程序
微信團隊的方案:
流程圖:

- 對現有程式碼改動量最小;
- 該方案不關注Application被哪個元件啟動。Activity ,Service ,Receiver ,ContentProvider 都滿足(與美團方案都相同的問題,假如開啟的不是Activity。這個時候彈出一個過渡的Activity就非常尷尬);
- 該方案不限制 Application ,Activity ,Service ,Receiver ,ContentProvider 繼續新增業務;
實現程式碼:
泡在網上的日子:其實你不知道MultiDex到底有多坑 單獨說一下 waitForDexopt
這個方法,這裡設定的10s(Honeycomb之前20s)的輪詢之後執行了 MultiDex.install
。此時在 mini
程序中 Multidex
可能還未完成安裝(我們專案目前一共3個dex, Multidex
的安裝耗時大概20s)。
public void waitForDexopt(Context base) { Intent intent = new Intent(); ComponentName componentName = new ComponentName( "com.zongwu", LoadResActivity.class.getName()); intent.setComponent(componentName); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); base.startActivity(intent); long startWait = System.currentTimeMillis (); long waitTime = 10 * 1000 ; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB_MR1 ) { waitTime = 20 * 1000 ;//實測發現某些場景下有些2.3版本有可能10s都不能完成optdex } while (needWait(base)) { try { long nowWait = System.currentTimeMillis() - startWait; LogUtils.d("loadDex" , "wait ms :" + nowWait); if (nowWait >= waitTime) { return; } Thread.sleep(200 ); } catch (InterruptedException e) { e.printStackTrace(); } } }
當啟動 :mini
程序後,主程序就會切換為後臺程序所以不存在ANR的問題。我們可以一直輪詢 needWait
,直到 Multidex
載入完成。
public void waitForDexopt(Context base) { /***部分程式碼省略***/ while (needWait(base)) { try { //long nowWait = System.currentTimeMillis() - startWait; //LogUtils.d("loadDex" , "wait ms :" + nowWait); //if (nowWait >= waitTime) { //return; //} Thread.sleep(200 ); } catch (InterruptedException e) { e.printStackTrace(); } } }
參考資料:
文章到這裡就全部講述完啦,若有其他需要交流的可以留言哦~!~!
想閱讀作者的更多文章,可以檢視我個人部落格 和公共號:
