MultiDex到底有多坑


google為什么要引入MultiDex?

dex指令是用16位寄存器來保存dex中的方法數,所以限制了在apk

中最大的方法數為65535,當超過這個最大值在編譯的時候會報

方法數超標的錯誤。

如何引入MultiDex?

1.修改gradle腳本來產生多dex。
2.修改manifest 使用MulitDexApplication。

步驟1.在gradle腳本里寫上:

  1. android {
  2. compileSdkVersion 21
  3. buildToolsVersion "21.1.0"
  4. defaultConfig {
  5. ...
  6. minSdkVersion 14
  7. targetSdkVersion 21
  8. ...
  9. // Enabling multidex support.
  10. multiDexEnabled true
  11. }
  12. ...
  13. }
  14. dependencies {
  15. compile 'com.android.support:multidex:1.0.0'
  16. }

步驟2. manifest聲明修改

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. package="com.example.android.multidex.myapplication">
  4. <application
  5. ...
  6. android:name="android.support.multidex.MultiDexApplication">
  7. ...
  8. </application>
  9. </manifest>

如果有自己的Application,繼承MulitDexApplication。如果當前代碼已經繼承自其它Application沒辦法修改那也行,就重寫 Application的attachBaseContext()這個方法。 

  1. @Override
  2. protected void attachBaseContext(Context base) {
  3. super.attachBaseContext(base);
  4. MultiDex.install(this);
  5. }

引入multiDex后會碰到的亢

1.在應用安裝到手機上的時候dex文件的安裝是復雜的(complex)有可能會因為第二個dex文件太大導致ANR。請用proguard優化你的代碼。
2.使用了mulitDex的App有可能在4.0(api level 14)以前的機器上無法啟動,因為Dalvik linearAlloc bug(Issue 22586) 。請多多測試自祈多福。用proguard優化你的代碼將減少該bug幾率。
3.使用了mulitDex的App在runtime期間有可能因為Dalvik linearAlloc limit (Issue 78035) Crash。該內存分配限制在 4.0版本被增大,但是5.0以下的機器上的Apps依然會存在這個限制。
4.主dex被dalvik虛擬機執行時候,哪些類必須在主dex文件里面這個問題比較復雜。build tools 可以搞定這個問題。但是如果你代碼存在反射和native的調用也不保證100%正確。

 

INSTALL_FAILED_DEXOPT 解決方案:

apk是一個zip壓縮包,dalvik每次加載apk都要從中解壓出class.dex文件,加載過程還涉及到dex的classes需要的雜七雜八的依賴庫的加載,真耗時間。於是Android決定優化一下這個問題,在app安裝到手機之后,系統運行dexopt程序對dex進行優化,將dex的依賴庫文件和一些輔助數據打包成odex文件。存放在cache/dalvik_cache目錄下。保存格式為apk路徑 @ apk名 @ classes.dex。這樣以空間換時間大大縮短讀取/加載dex文件的過程。
那剛才那個bug是啥問題呢,原來dexopt程序的dalvik分配一塊內存來統計你的app的dex里面的classes的信息,由於classes太多方法太多超過這個linearAlloc 的限制 。那減小dex的大小就可以咯。

gradle腳本如下:

  1. android.applicationVariants.all {
  2. variant ->
  3. dex.doFirst{
  4. dex->
  5. if (dex.additionalParameters == null) {
  6. dex.additionalParameters = []
  7. }
  8. dex.additionalParameters += '--set-max-idx-number=48000'
  9. }
  10. }

--set-max-idx-number= 用於控制每一個dex的最大方法個數,寫小一點可以產生好幾個dex。 踩過更多坑的FB的工程師表示這個linearAlloc的限制不僅僅在安裝時候的dexopt程序里7,還在你的app的dalvik rumtime里。(很顯然啊dvk vm的宿主進程fork自於同一個母體啊)。為了表示對這個坑的不滿以及對Google的產品表示遺憾,FB工程師Read The Fucking Source Code找到了一個hack方案。這個linearAlloc的size定義在c層而且是一個全局變量,他們通過對結構體的size的計算成功覆蓋了該值的內容,這里要特別感謝C語言的指針和內存的設計。C的世界里,You Are The King of This World。當然實際情況是大部分用戶用這把利刃割傷了自己

 

首次安裝運行ANR問題

於是發現 是 install dex + dexopt 時間太長!
梳理流程:
安裝完app點擊圖標之后,系統木有發現對應的process,於是從該apk抽取classes.dex(主dex) 加載,觸發 一次dexopt。 
App 的laucherActivity准備啟動 ,觸發Application啟動, 
Application的 onattach()方法調用,這時候MultiDex.install()調用,classes2.dex 被install,再次觸發dexopt。
然后Applicaition onCreate()執行。
然后 launcher Activity真的起來了。
這些必須在5s內完成不然就ANR給你看!

 

問題到現在變成了:既希望在Application的attachContext()方法里同步加載secondary.dex,又不希望卡住UI線程。如果思路限制在線程異步化上,確實不可能實現。於是發現了微信開發團隊的這篇文章。該文章介紹了關於這一問題 FB/QQ/微信的解決方案。FB的解決思路特別贊,讓Launcher Activity在另外一個進程啟動!當然這個Launcher Activity就是用來load dex 的 ,load完成就啟動Main Activity。
微信這篇文章給出了一個非常重要的觀點:安裝完成之后第一次啟動時,是secondary.dex的dexopt花費了更多的時間。認識到這點非常重要,使得問題又轉化為:在不阻塞UI線程的前提下,完成dexopt,以后都不需要再次dexopt,所以可以在UI線程install dex 了!文章最后給了一個對FB方案的改進版。
仔細讀完感覺完全可行。
1.對現有代碼改動量最小。
2.該方案不關注Application被哪個組件啟動。Activity ,Service ,Receiver ,ContentProvider 都滿足。(有個問題要說明:如細心網友指出的那樣,新安裝還未啟動但是收到Receiver的場景下,會導致Load界面出現。這個場景實際出現幾率比較少,且僅出現一次。可以接受。)  
3.該方案不限制 Application ,Activity ,Service ,Receiver ,ContentProvider 繼續新增業務。

 

主要的原理:application啟動了LoadDexActivity之后,自身不再是前台進程所以怎么hold 線程都不會ANR

 

參考鏈接:http://blog.zongwu233.com/the-touble-of-multidex/?from=groupmessage&isappinstalled=0

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM