美文网首页workAndroid开发Android技术知识
Multidex(二)之Dex预加载优化

Multidex(二)之Dex预加载优化

作者: 未来的理想 | 来源:发表于2016-12-18 20:32 被阅读2971次

一、前言

Multidex(一)之源码解析中我们介绍到MultiDex极有可能出现ANR(Application No Response)的问题,秒秒钟卡死我们的应用,用户肯定忍不了要怒卸载啊!作为追(被)求(逼)完(无)美(耐)的程序员哥哥,我们怎能作壁上观?Google不做好的事情,我们就自己扛起来!那么如何对MultiDex这个方案做优化让它变成好同志呢?<br />

本文就带你实战MultiDex的预加载优化。<br />

二、分析

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

三、优化方案工作流程图

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

四、代码实战

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

@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("dexoptdone", true).commit();
                killCurrentProcess();
            } catch (Exception e) {
                LogUtil.e("loadDex", e.getLocalizedMessage());
                killCurrentProcess();
            }
        }
    }.start();
}

在AndroidManifest中配置:<br />

<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-18 下午6.04.32.png

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

  • 第一次打开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,程序猿就是为了解决挑战而生的,异步加载确实是个正常又合理的想法,那这个想法怎么落地呢?欢迎关注下一篇文章。
<br />
欢迎关注微信公众号:定期分享Java、Android干货!

欢迎关注

相关文章

网友评论

  • 32135776c20b:老哥,在 attachBaseContext 中 主线程执行 MultiDex.install 耗时操作时,并不会引起 ANR, 只是这段时间 主线程不能处理界面,屏幕上将会是 白屏状态,windowBackgroud 可以让屏幕设置一个图片背景, 这 和你 再启一个进程 并在此进程内创建一个LoadingActivity 本质 是不是 一样的效果?
    另外你主进程 里 sleep 100 毫秒,这个100是如何来的,实测multidex 耗时在 几秒 的 级别,我们无法确定何时 子进程中 multiDex.install完成了。 对于这个问题 是否可以用Messenger进程间通信来解决,比如子进程完成 install时 通知 主进程, 但还有个疑问,主进程都没有完全启动起来,Messenger 是否进行通信,这是个问题。
    未来的理想:@晓游 赞
    32135776c20b:发现了,楼主是通过 sp 的 MODE_MULTI_PROCESS 模式来实现 进程间通信的。 good
  • 李云龙_:还有个问题,老哥,我想在 PreLoadDexSplashActivity 异步加载完 MultiDex 后不 killCurrentProcess()继续显示该界面,等 Application 也 MultiDex.install(this) 完成后通知该界面再执行后续的操作,请问在 PreLoadDexSplashActivity 里注册个广播行吗?不会出啥问题吧
  • 李云龙_:老哥,isMainProcess(this)能把代码写全吗?最好将这一系列文章的demo 上传到 github,要不看着费劲,,
  • 冉桓彬:技术哥, 看评论都在质疑实用性啊:sob:
  • gwball:我在Application的主线程睡眠30秒,没有发生ANR,是为什么呢?
    未来的理想:你在sleep的时候狂点屏幕试试
  • sososeen09:PreLoadDexActivity也必须放在main dex中了
    未来的理想:@sososeen09 对的
  • f9255619252a:貌似我试了一下直接在主线程也不会发生anr?我的代码:

    long time1 = System.currentTimeMillis();
    try {
    Thread.sleep(30000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    MultiDex.install(this);
    Log.i(TAG,"check multi-dex install time,time diff:"+(System.currentTimeMillis()-time1));
  • d68fb3e149f6:这种方式加载可以解决ANR问题,但是能提升应用首次启动速度么?现在应用启动使用MultiDex导致首次启动很慢。
    fc51016db31b:这样不如在UI进程开启新的线程,然后UI线程等待。这样更加方便
    未来的理想:@thomas_40bc 不会出现anr,不满足anr的触发场景
    未来的理想:@Lee_c3ff 这种方式不能提升App的启动速度。dalvik下使用Multidex首次启动就是很慢,因为再做dex2odex的优化。第三篇文章dex异步加载优化可以解决启动慢的问题,但是拆分dex的难度以及维护成本比较大,因此我个人比较推荐使用第二种方式来做Dex的优化方案,虽然是会慢,但是只有第一次慢。
  • a5c6569f2ee2:校验机制不够严谨,如果覆盖升级,仍然需要走opt优化,但这时如果单纯依据SharedPreferences 这个使能将会判断不准确,最好使用dex签名进行校验,这样只要签名不匹配,就去执行异步loaddex。
    未来的理想:对的,此处需要对校验机制进行补强,本文主要是写开进程进行MultiDex优化的方案,对这个细节并没有特意关注。
  • 7e043151b847:按你的说法,那么就是说在Application中执行的业务加载必须不能在子进程中加载了。
    未来的理想:不是的。单开的进程只是第一次进行dexopt的优化,不干任何别的操作,在预加载优化的过程中,Application应该是在等待状态。
  • f258ef0b89e5:你的这种方式中间会出现黑屏,用4.4的机器去测试的话。
    f9255619252a:我猜这种情况可以把那个PrexxxActivity的contentview设置成一张启动页的图片,做一个加载中的动画应该就可以了
    未来的理想:看下是不是Activity主题的原因。

本文标题:Multidex(二)之Dex预加载优化

本文链接:https://www.haomeiwen.com/subject/yzczmttx.html