从这里认识Multidex源码(中)

kcoufee
发布于 2020-8-30 15:07
浏览
0收藏

一、前言
在从这里认识Multidex源码(上)中我们介绍了MultiDex很有可能出现ANR(Application No Response)的问题,并且我们的应用程序在几秒钟内卡死。 必须麻烦用户卸载! 作为追求完美的程序员兄弟,我们如何更好的站在这个领域? 如果Google表现不好,我们会自己承担! 那么,如何优化MultiDex解决方案使其变优秀?
本文将带您完成MultiDex的实际预加载优化。

 

二、分析
从这里认识Multidex源码(上)中分析过MultiDex第一次加载出现ANR的原因是因为提取Dex以及DexOpt这两个过程都是耗时的操作,而且他们还都发生在主进程。稍等:主进程,ANR,脑袋里好像闪现一道灵光,既然在主进程执行会产生ANR,那能不能换个进程执行呢?橘生淮南则为橘,生于淮北则为枳;换个进程说不定就有突破点。说干就干,凭借程序员机智的大脑,分毫之间,一个优化方案的雏形已经了然于胸:****App第一次启动时单独开一个额外优化的进程率先进行Dex提取以及DexOpt的操作,与此同时主进程在后台等待,优化的进程执行完毕之后通知主进程继续往下执行,主进程在执行MultiDex.install时发现已经是提前优化好了Dex,直接执行,非常快,毫秒级别,不会造成卡顿,愉快的往下继续执行。****

三、优化方案工作流程图

四、代码实战
在Application的attachBaseContext中执行优化方案;

@Overrideprotected 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中执行优化的操作,完成后修改标示; 

@Overridepublic 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("dexoptdone", 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"

    />

运行看一下效果

从这里认识Multidex源码(中)-鸿蒙开发者社区

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

可以通过Log看到,在优化进程中Dex的提取以及Dexopt的操作耗时近4秒,而在主进程的第二次执行则耗时16毫秒,耗时发生在优化进程中的线程中,主进程实际执行MultiDex.install的时候耗时极其短暂;再也不会出现ANR的困扰了。 

· 第一次打开App,会出现PreLoadDexActivity,略显突兀,可以再应用的闪屏页加上这段逻辑,根据标示判断究竟执行正常逻辑还是优化的逻辑。

· 关于SharedPreferences进程间不安全的问题:此处的使用只是单向的读写,因而不会有这个场景。

五、问题
1、为什么执行优化操作的时候判断只有在主进程以及SDK版本5.0以下才执行呢?
如果App是多进程架构的话,Application会执行多次,这个优化过程无需执行多次;而在SDK版本5.0及以上,默认使用ART虚拟机,与Dalvik的区别在于安装时已经将全部的Class.dex转换为了oat文件,优化过程在安装时已经完成;因此无需执行。

2、为什么主进程此时不会ANR?
回忆下ANR的发生场景:Service、BroadCastReceiver、ContentProvider的TimeOut;输入事件的TimeOut等。当出现ANR时,都会最终调用到AMS的appNotResponding()方法。
因为主进程此时已经进入后台,不响应Android屏幕事件。同时也不存在以上发生ANR的场景,因此主进程在后台Sleep,不会产生ANR。

3、在优化的进程中只是开启了一个线程提前做了MultiDex的工作,那为什么不直接在主进程中开启一个子线程做同样工作呢?
Good Question,不愧是善于思考的程序猿!在主进程中直接开启一个子线程确实是可以避免ANR的问题,但是有没有想到,此时主进程中调用到的类,可能会因为SecondaryDex的优化尚未完成或者没有被加入到ClassLoader中而导致画面太美不敢看的ClassNotFoundException。

那是不是就宣判了这个想法的死刑呢?No,No,No,程序猿就是为了解决挑战而生的,异步加载确实是个正常又合理的想法,那这个想法怎么落地呢?欢迎关注下一篇文章。
 

分类
已于2020-8-31 17:24:48修改
收藏
回复
举报
回复
    相关推荐