天天看點

關于Android MultiDex的問題

這段時間研究了一下Android MultiDex,這個東西乍看起來很簡單,其實涉及到很多東西,本文就來講講這個,想到哪就說到哪。

分包主要是因為包太大了,低端手機安裝可能會失敗,或者Dex加載的時候會崩潰,是以分成多個包,跟啟動相關的放在主Dex,其餘的不那麼緊急的放在副Dex,然後啟動的時候去動态加載。

首先來看看主Dex,所謂跟啟動相關的主要就是四大元件,是以四大元件直接引用到的類都要放在主Dex中,否則啟動時找不到類就悲劇了,畢竟動态加載副Dex可能會挺耗時的。Android Studio的build tool會自動幫你完成分包,将Android Manifest中定義的四大元件直接引用到的類都放在主Dex中,其餘的丢到副Dex。不過也不保證百分百沒問題,比如你用反射或者So引用副Dex中的類也許這個腳本就會瞎掉了。當然我們也可以自己寫個腳本去分析APP啟動需要引用到的類,方法是在APP啟動完畢時打出ClassLoader中所有已加載過的類就行了。

接下來再來說說這個副Dex加載的問題,如果這個副Dex比較大,加載起來可能挺耗時,為什麼呢?耗時主要在于Dexopt優化。Dexopt優化時機通常有兩個,一個是APP安裝的時候,這時候隻會優化主Dex。第二個是ClassLoader加載Dex時,不過如果已經優化過了就不會再優化了。是以在APP啟動時動态加載副Dex僅在第一次才會挺耗時,之後就非常快了。現在關鍵就在于怎麼處理第一次加載Dex耗時的問題,因為是在Application裡加載的,容易ANR。我們可以另開一個線程來加載,不過可能還沒等到加載完,主Dex中就要引用到副Dex裡的類了,當然這個機率不會太大,因為我們前面提到過主Dex裡已經給所有要直接引用的類都包含進去了,是以短時間内不會出岔子。但是事情就怕萬一,如果真出現這種情況,我們不能眼睜睜看着程式崩潰。

接下來就讨論幾種解決方案:

第一種,美團的方案,攔截Activity的啟動過程,當發現要啟動的Activity還沒加載好的時候,直接改成跳到另一個LoadingActivity,等加載完畢後再跳到真正的目标。至于怎麼攔截Activity的啟動,可以參考我之前的文章如何Hook Activity的啟動。不過對于其餘的元件比如Service、廣播這種背景型的就有點奇怪了,冷不丁會冒出一個LoadingActivity顯得不那麼自然。相比之下,下面的解決方案會好一些。

第二種,微信的解決方案,由于Dex隻有沒有優化過的時候才會調用Dexopt,是以也就是說第一次才會耗時,之後都很快,那我們重點解決第一次就好了,隻是第一次慢點使用者基本還可以接受。在Application啟動時如果發現Dex未優化,我們就啟動LoadingActivity,同時Application中繼續加載Dex,即便耗時也不會ANR。因為LoadingActivity跑在新的程序,并且是目前的前台程序,而Application成了背景程序,不會導緻ANR。當Dex加載完畢後就跳轉到真正的Launcher Activity,并殺掉LoadingActivity。這種解決方案總體來說還是不錯的,唯一的問題可能是LoadingActivity要啟動在一個新程序,也許會比較耗時,我們可以優化一下,判斷Dex加載會不會耗時,如果會耗時就放在一個子線程中加載,然後在目前程序中跳轉到LoadingActivity,等到Dex加載完了再跳到Launcher Activity。如果Dex加載不會耗時,就直接在主線程中加載了,然後也沒必要跳到Loading Activity了。