在Multidex(一)之源碼解析中咱們介紹到MultiDex極有可能出現ANR(Application No Response)的問題,秒秒鐘卡死咱們的應用,用戶確定忍不了要怒卸載啊!做爲追(被)求(逼)完(無)美(耐)的程序員哥哥,咱們怎能做壁上觀?Google不作好的事情,咱們就本身扛起來!那麼如何對MultiDex這個方案作優化讓它變成好同志呢?
javascript
本文就帶你實戰MultiDex的預加載優化。
java
Multidex(一)之源碼解析中分析過MultiDex第一次加載出現ANR的緣由是由於提取Dex以及DexOpt這兩個過程都是耗時的操做,並且他們還都發生在主進程。稍等:主進程,ANR,腦殼裏好像閃現一道靈光,既然在主進程執行會產生ANR,那能不能換個進程執行呢?橘生淮南則爲橘,生於淮北則爲枳;換個進程說不定就有突破點。說幹就幹,憑藉程序員機智的大腦,分毫之間,一個優化方案的雛形已經瞭然於胸:App第一次啓動時單獨開一個額外優化的進程率先進行Dex提取以及DexOpt的操做,與此同時主進程在後臺等待,優化的進程執行完畢以後通知主進程繼續往下執行,主進程在執行MultiDex.install時發現已是提早優化好了Dex,直接執行,很是快,毫秒級別,不會形成卡頓,愉快的往下繼續執行。android
在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"
/>複製代碼
運行看一下效果
架構
能夠經過Log看到,在優化進程中Dex的提取以及Dexopt的操做耗時近4秒,而在主進程的第二次執行則耗時16毫秒,耗時發生在優化進程中的線程中,主進程實際執行MultiDex.install的時候耗時極其短暫;不再會出現ANR的困擾了。
app
一、爲何執行優化操做的時候判斷只有在主進程以及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乾貨!