Android Dex分包原理

為什么要分包?

1、65536問題

  • 導(dǎo)致因素

    隨著項(xiàng)目apk的龐大以及加入更多的第三方庫,app的方法數(shù)已經(jīng)超過了65536,會(huì)導(dǎo)致程序根本跑不起來。

  • 原因
    在生成.dex文件后由于有很多冗余的資源,所以Android中會(huì)對(duì)dex文件進(jìn)行優(yōu)化,Davlik模式下利用dexopt工具進(jìn)行優(yōu)化,而dexopt有兩個(gè)問題:

    • Dexopt 會(huì)把每一個(gè)類的方法 id 檢索起來,存在一個(gè)鏈表結(jié)構(gòu)里面,但是這個(gè)鏈表的長(zhǎng)度是用一個(gè) short 類型來保存的,導(dǎo)致了方法 id 的數(shù)目不能夠超過65536個(gè), 當(dāng)一個(gè)項(xiàng)目足夠大的時(shí)候,顯然這個(gè)方法數(shù)的上限是不夠的;
    • Dexopt 使用 LinearAlloc 來存儲(chǔ)應(yīng)用的方法信息, Dalvik LinearAlloc 是一個(gè)固定大小的緩沖區(qū)。在Android 版本的歷史上,LinearAlloc 分別經(jīng)歷了4M/5M/8M/16M限制。Android 2.2和2.3的緩沖區(qū)只有5MB,Android 4.x提高到了8MB 或16MB。當(dāng)方法數(shù)量過多導(dǎo)致超出緩沖區(qū)大小時(shí),也會(huì)造成dexopt崩潰;
  • ART模式下 ,采用的是dexoat工具,對(duì)應(yīng)生成art虛擬執(zhí)行可執(zhí)行的.oat文件,這個(gè)是包含多個(gè)dex文件;

2、怎么解決這個(gè)問題

  • 在gradle中添加MultiDex支持,加載classes2.dex
multiDexEnabled true
  • 執(zhí)行MultiDex.install()
@Override protected void attachBaseContext (Context base) {
    super.attachBaseContext(base);
    MultiDex.install(this);
}

分包導(dǎo)致的問題

  1. API14 之前的不能支持分包 Dalvik linearalloc bug

  2. 在冷啟動(dòng)時(shí)因?yàn)樾枰惭bdex文件,如果dex文件過大時(shí),處理時(shí)間過長(zhǎng),很容易引發(fā)ANR(Application Not Responding);

  3. 采用MultiDex方案的應(yīng)用因?yàn)樾枰暾?qǐng)一個(gè)很大的內(nèi)存,在運(yùn)行時(shí)可能導(dǎo)致程序的崩潰,這個(gè)主要是因?yàn)镈alvik linearAlloc 的一個(gè)限制,這個(gè)限制在 Android 4.0 (API level 14)已經(jīng)增加了, 應(yīng)用也有可能在低于 Android 5.0 (API level 21)版本的機(jī)器上觸發(fā)這個(gè)限制;

  4. 分包后,不同依賴項(xiàng)目間的dex文件函數(shù)相互調(diào)用,報(bào)錯(cuò)找不到方法

Android系統(tǒng)對(duì)分包的影響

  • Android 5.0以下:
    運(yùn)行在Davlik虛擬機(jī)上,優(yōu)化使用dexopt工具并分包,每次運(yùn)行先加載主包,然后反射子包,存在主包子包的先后問題;

  • Android 5.0以上:
    運(yùn)行在ART虛擬機(jī)上,優(yōu)化使用dexoat工具,生成多個(gè)包含dex文件的.oat文件,.oat文件是混合了主包子包,已經(jīng)在APK安裝時(shí)生成,故程序運(yùn)行起來不存在主包子包的加載先后問題;

MultiDex的基本原理

通過DexFile來加載Secondary DEX,并存放在BaseDexClassLoaderDexPathList中。

解決分包導(dǎo)致調(diào)用找不到對(duì)應(yīng)類

1、微信加載方案

首次加載在地球中頁中, 并用線程去加載(但是 5.0 之前加載 dex 時(shí)還是會(huì)掛起主線程一段時(shí)間(不是全程都掛起))。

  • dex 形式
    微信是將包放在 assets 目錄下的,在加載 Dex 的代碼時(shí),實(shí)際上傳進(jìn)去的是 zip,在加載前需要驗(yàn)證 MD5,確保所加載的 Dex 沒有被篡改。

  • dex 類分包規(guī)則
    分包規(guī)則即將所有 Application、ContentProvider 以及所有 export 的 Activity、Service 、Receiver 的間接依賴集都必須放在 主 dex

  • 加載 dex 的方式
    加載邏輯這邊主要判斷是否已經(jīng) dexopt,若已經(jīng) dexopt,即放在 attachBaseContext 加載,反之放于地球中用線程加載。怎么判斷?因?yàn)樵谖⑿胖?,若判?revision 改變,即將 dex 以及 dexopt 目錄清空。只需簡(jiǎn)單判斷兩個(gè)目錄 dex 名稱、數(shù)量是否與配置文件的一致。

總的來說,這種方案用戶體驗(yàn)較好,缺點(diǎn)在于太過復(fù)雜,每次都需重新掃描依賴集,而且使用的是比較大的間接依賴集。

2、 Facebook 加載方案

Facebook的思路是將 MultiDex.install() 操作放在另外一個(gè)經(jīng)常進(jìn)行的。

  • dex 形式
    與微信相同。

  • dex 類分包規(guī)則
    Facebook 將加載 dex 的邏輯單獨(dú)放于一個(gè)單獨(dú)的 nodex 進(jìn)程中。

  <activity 
    android:exported="false"
    android:process=":nodex"
     android:name="com.facebook.nodex.startup.splashscreen.NodexSplashActivity">

所有的依賴集為 Application、NodexSplashActivity 的間接依賴集即可。

  • 加載 dex 的方式
    因?yàn)?NodexSplashActivity 的 intent-filter 指定為 MainLAUNCHER ,所以一打開 App 首先拉起 nodex 進(jìn)程,然后打開 NodexSplashActivity 進(jìn)行 MultiDex.install() 。如果已經(jīng)進(jìn)行了 dexpot 操作的話就直接跳轉(zhuǎn)主界面,沒有的話就等待 dexpot 操作完成再跳轉(zhuǎn)主界面。

這種方式好處在于依賴集非常簡(jiǎn)單,同時(shí)首次加載 dex 時(shí)也不會(huì)卡死。但是它的缺點(diǎn)也很明顯,即每次啟動(dòng)主進(jìn)程時(shí),都需先啟動(dòng) nodex 進(jìn)程。盡管 nodex 進(jìn)程邏輯非常簡(jiǎn)單,這也需100ms以上。

3、美團(tuán)加載方案

  • dex 形式
    在 gradle 生成 dex 文件的這步中,自定義一個(gè) task 來干預(yù) dex 的生產(chǎn)過程,從而產(chǎn)生多個(gè) dex 。

    tasks.whenTaskAdded { task ->
     if (task.name.startsWith('proguard') && (task.name.endsWith('Debug') || task.name.endsWith('Release'))) {
     task.doLast {
     makeDexFileAfterProguardJar();
     }
     task.doFirst {
     delete "${project.buildDir}/intermediates/classes-proguard";

     String flavor = task.name.substring('proguard'.length(), task.name.lastIndexOf(task.name.endsWith('Debug') ? "Debug" : "Release"));
     generateMainIndexKeepList(flavor.toLowerCase());
     }
     } else if (task.name.startsWith('zipalign') && (task.name.endsWith('Debug') || task.name.endsWith('Release'))) {
     task.doFirst {
     ensureMultiDexInApk();
     }
     }
    }
  • dex 類分包規(guī)則

    把 Service、Receiver、Provider 涉及到的代碼都放到主 dex 中,而把 Activity 涉及到的代碼進(jìn)行了一定的拆分,把首頁 Activity、Laucher Activity 、歡迎頁的 Activity 、城市列表頁 Activity 等所依賴的 class 放到了主 dex 中,把二級(jí)、三級(jí)頁面的 Activity 以及業(yè)務(wù)頻道的代碼放到了第二個(gè) dex 中,為了減少人工分析 class 的依賴所帶了的不可維護(hù)性和高風(fēng)險(xiǎn)性,美團(tuán)編寫了一個(gè)能夠自動(dòng)分析 class 依賴的腳本, 從而能夠保證?主 dex 包含 class 以及他們所依賴的所有 class 都在其內(nèi),這樣這個(gè)腳本就會(huì)在打包之前自動(dòng)分析出啟動(dòng)到主 dex 所涉及的所有代碼,保證主 dex 運(yùn)行正常。

  • 加載 dex 的方式
    通過分析 Activity 的啟動(dòng)過程,發(fā)現(xiàn) Activity 是由 ActivityThread 通過 Instrumentation 來啟動(dòng)的,那么是否可以在 Instrumentation 中做一定的手腳呢?通過分析代碼 ActivityThread 和 Instrumentation 發(fā)現(xiàn),Instrumentation 有關(guān) Activity 啟動(dòng)相關(guān)的方法大概有:execStartActivity、 newActivity 等等,這樣就可以在這些方法中添加代碼邏輯進(jìn)行判斷這個(gè) class 是否加載了,如果加載則直接啟動(dòng)這個(gè) Activity,如果沒有加載完成則啟動(dòng)一個(gè)等待的 Activity 顯示給用戶,然后在這個(gè) Activity 中等待后臺(tái)第二個(gè) dex 加載完成,完成后自動(dòng)跳轉(zhuǎn)到用戶實(shí)際要跳轉(zhuǎn)的 Activity;這樣在代碼充分解耦合,以及每個(gè)業(yè)務(wù)代碼能夠做到顆?;那疤嵯?,就做到第二個(gè) dex 的按需加載了。

美團(tuán)的這種方式對(duì)主 dex 的要求非常高,因?yàn)榈诙€(gè) dex 是等到需要的時(shí)候再去加載。重寫Instrumentation 的 execStartActivity 方法,hook 跳轉(zhuǎn) Activity 的總?cè)肟谧雠袛啵绻?dāng)前第二個(gè) dex 還沒有加載完成,就彈一個(gè) loading Activity等待加載完成。

最后,希望此篇博客對(duì)大家有所幫助,歡迎提出問題及建議共同探討,如有興趣可以關(guān)注我的博客,謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 最近開發(fā)中我們發(fā)現(xiàn),我們的產(chǎn)品在Android設(shè)備版本低于5.0以下第一次安裝啟動(dòng)會(huì)出現(xiàn)黑屏、ANR等情況。...
    miraclehen閱讀 3,797評(píng)論 2 11
  • 為什么需要對(duì)Dex進(jìn)行分包 Android在安裝應(yīng)用的過程中,系統(tǒng)會(huì)運(yùn)行一個(gè)名為DexOpt的程序?yàn)樵搼?yīng)用在當(dāng)前機(jī)...
    Boreas_su閱讀 4,438評(píng)論 0 9
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,987評(píng)論 25 709
  • 要意識(shí)到 泛泛之交的關(guān)系是很脆弱的 千萬別越界
    ziziseven閱讀 214評(píng)論 0 0
  • 一個(gè)喜歡拍照,喜歡獨(dú)自行走的彷徨青年,以下是這一年拍的照片的略影,大部分都保存在電腦上,上傳麻煩就簡(jiǎn)單找了幾張手機(jī)...
    1988年的冬季閱讀 987評(píng)論 13 13

友情鏈接更多精彩內(nèi)容