Multidex(二)之 Dex 預加載優化

1、前言

Multidex(一)之源碼解析中咱們介紹到MultiDex極有可能出現ANR(Application No Response)的問題,秒秒鐘卡死咱們的應用,用戶確定忍不了要怒卸載啊!做爲追(被)求(逼)完(無)美(耐)的程序員哥哥,咱們怎能做壁上觀?Google不作好的事情,咱們就本身扛起來!那麼如何對MultiDex這個方案作優化讓它變成好同志呢?
javascript

本文就帶你實戰MultiDex的預加載優化。
java

2、分析

Multidex(一)之源碼解析中分析過MultiDex第一次加載出現ANR的緣由是由於提取Dex以及DexOpt這兩個過程都是耗時的操做,並且他們還都發生在主進程。稍等:主進程,ANR,腦殼裏好像閃現一道靈光,既然在主進程執行會產生ANR,那能不能換個進程執行呢?橘生淮南則爲橘,生於淮北則爲枳;換個進程說不定就有突破點。說幹就幹,憑藉程序員機智的大腦,分毫之間,一個優化方案的雛形已經瞭然於胸:App第一次啓動時單獨開一個額外優化的進程率先進行Dex提取以及DexOpt的操做,與此同時主進程在後臺等待,優化的進程執行完畢以後通知主進程繼續往下執行,主進程在執行MultiDex.install時發現已是提早優化好了Dex,直接執行,很是快,毫秒級別,不會形成卡頓,愉快的往下繼續執行。android

3、優化方案工做流程圖

屏幕快照 2016-12-18 下午7.34.12.png

4、代碼實戰

在Application的attachBaseContext中執行優化方案;程序員

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    //只有主進程以及SDK版本5.0如下才走。
    if (isMainProcess(Application.this) && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        if (!dexOptDone(base)) {
            preLoadDex(base);
        }
        long startTime = System.currentTimeMillis();
        MultiDex.install(this);
        LogUtil.i(TAG,"MainProcessCostTime:"+(System.currentTimeMillis() - startTime));
    }
}

/** * 當前版本是否進行過DexOpt操做。 * @param context * @return */
private boolean dexOptDone(Context context) {
    SharedPreferences sp = context.getSharedPreferences(
            DeviceUtil.getVersionName(context), MODE_MULTI_PROCESS);
    return sp.getBoolean("dexoptdone", false);
}

/** * 在單獨進程中提早進行DexOpt的優化操做;主進程進入等待狀態。 * * @param base */
public void preLoadDex(Context base) {
    Intent intent = new Intent(Application.this, PreLoadDexActivity.class);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    base.startActivity(intent);
    while (!dexOptDone(base)) {
        try {
            //主線程開始等待;直到優化進程完成了DexOpt操做。
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}複製代碼

而後在PreLoadDexActivity中執行優化的操做,完成後修改標示;
安全

@Override
public void onCreate(Bundle savedInstanceState) {
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    super.onCreate(savedInstanceState);
    overridePendingTransition(0, 0);//取消掉系統默認的動畫。
    getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
    setContentView(R.layout.predexlayout);

    new Thread() {
        @Override
        public void run() {
            super.run();
            try {
                long time = System.currentTimeMillis();
                MultiDex.install(getApplication());
                LogUtil.i("lz", "PreLoadDexActivityCostTime:" + (System.currentTimeMillis() - time));
                SharedPreferences sp = getSharedPreferences(
                        DeviceUtil.getVersionName(PreLoadDexActivity.this), MODE_MULTI_PROCESS);
                sp.edit().putBoolean("dexdone", true).commit();
                killCurrentProcess();
            } catch (Exception e) {
                LogUtil.e("loadDex", e.getLocalizedMessage());
                killCurrentProcess();
            }
        }
    }.start();
}複製代碼

在AndroidManifest中配置:
微信

<activity android:name=".PreLoadDexActivity"
    android:process=":preloaddex"
    android:alwaysRetainTaskState= "false"
    android:theme="@style/PreLoadStyle"
    android:launchMode= "singleTask"
    android:excludeFromRecents= "true"
    android:screenOrientation= "portrait"
    />複製代碼

運行看一下效果
架構

屏幕快照 2016-12-17 下午1.15.49.png

能夠經過Log看到,在優化進程中Dex的提取以及Dexopt的操做耗時近4秒,而在主進程的第二次執行則耗時16毫秒,耗時發生在優化進程中的線程中,主進程實際執行MultiDex.install的時候耗時極其短暫;不再會出現ANR的困擾了。
app

  • 第一次打開App,會出現PreLoadDexActivity,略顯突兀,能夠再應用的閃屏頁加上這段邏輯,根據標示判斷究竟執行正常邏輯仍是優化的邏輯。
  • 關於SharedPreferences進程間不安全的問題:此處的使用只是單向的讀寫,於是不會有這個場景。

5、問題

一、爲何執行優化操做的時候判斷只有在主進程以及SDK版本5.0如下才執行呢?
若是App是多進程架構的話,Application會執行屢次,這個優化過程無需執行屢次;而在SDK版本5.0及以上,默認使用ART虛擬機,與Dalvik的區別在於安裝時已經將所有的Class.dex轉換爲了oat文件,優化過程在安裝時已經完成;所以無需執行。異步

二、爲何主進程此時不會ANR?
回憶下ANR的發生場景:Service、BroadCastReceiver、ContentProvider的TimeOut;輸入事件的TimeOut等。當出現ANR時,都會最終調用到AMS的appNotResponding()方法
由於主進程此時已經進入後臺,不響應Android屏幕事件。同時也不存在以上發生ANR的場景,所以主進程在後臺Sleep,不會產生ANR。ide

三、在優化的進程中只是開啓了一個線程提早作了MultiDex的工做,那爲何不直接在主進程中開啓一個子線程作一樣工做呢?
Good Question,不愧是善於思考的程序猿!在主進程中直接開啓一個子線程確實是能夠避免ANR的問題,可是有沒有想到,此時主進程中調用到的類,可能會由於SecondaryDex的優化還沒有完成或者沒有被加入到ClassLoader中而致使畫面太美不敢看的ClassNotFoundException。

那是否是就宣判了這個想法的死刑呢?No,No,No,程序猿就是爲了解決挑戰而生的,異步加載確實是個正常又合理的想法,那這個想法怎麼落地呢?歡迎關注下一篇文章。


歡迎關注微信公衆號:按期分享Java、Android乾貨!

歡迎關注
相關文章
相關標籤/搜索