【Android】Google Multidex使用方法

本文介绍了Android应用因方法数超过65536限制时采用Google MultiDex进行dex分包的解决方案。内容包括启用MultiDex的Gradle配置,AndroidManifest.xml的应用声明,以及解决低端机型可能出现的INSTALL_FAILED_DEXOPT、ANR等问题的方法,包括子进程加载dex优化策略。

对于Android进行dex分包的原因以及策略,这里就不再进行详细的叙述,具体的可以参考这篇文章,是我转载自美团技术分享的文章:
http://blog.csdn.net/gitzzp/article/details/51691097
文章中,美团的大神已经将几种不同策略的优缺点说的非常的详尽,然后对于普通的小公司而言,并没有太多的人员精力来投入到其中,综合来说,使用google的MultiDex还是一个非常好的解决方法的。

切入正题,以Android studio为例,要使用MultiDex,分为以下几步:

  • 首先,修改Gradle配置文件,启用MultiDex并包含MultiDex支持:
android { 

    compileSdkVersion 21 buildToolsVersion "21.1.0" 

    defaultConfig { 

        ... 

        minSdkVersion 14 

        targetSdkVersion 21 

        ... 

        // Enabling MultiDex support. 

        multiDexEnabled true 

        } 

        ... 

    } 

    dependencies { compile 'com.android.support:MultiDex:1.0.0' 

} 
  • 然后,让应用支持多DEX文件。在官方文档中描述了三种可选方法:
    1. 在AndroidManifest.xml的application中声明android.support.MultiDex.MultiDexApplication;
    2. 如果你已经有自己的Application类,让其继承MultiDexApplication;
    3. 如果你的Application类已经继承自其它类,你不想/能修改它,那么可以重写attachBaseContext()方法:
@Override  

protected void attachBaseContext(Context base) { 

    super.attachBaseContext(base); 

    MultiDex.install(this); 

} 

如果没有在manifest中对application进行声明的话,还需要对application添加声明:

<application 
    ... 
    android:name="android.support.MultiDex.MultiDexApplication"> 
    ... 
</application>


此时MultiDex已经集成完毕。可以运行起来,但在实际中,你会发现在许多低端机型上边会出现各种各样的问题,例如INSTALL_FAILED_DEXOPT、ANR等问题,具体原因参见这里:
http://www.52pojie.cn/thread-435851-1-1.html


解决方法如下:

  • build.gradle中进行配置(注意,这里的配置仅适用于gradle1.5 以下,后边gradle语法进行了更新,请自行查询)
/**
 * dex 包的切分,让其保持每一个dex体积<4M ,value<=48000
 */
android.applicationVariants.all {
    variant ->
        dex.doFirst {
            dex ->
                if (dex.additionalParameters == null) {
                    dex.additionalParameters = []
                }
                dex.additionalParameters += '--set-max-idx-number=48000'
        }
}

上述代码限制了每个dex包的大小,解决了部分机型INSTALL_FAILED_DEXOPT的问题

  • Application中代码
    /**
     * 调用多包分类处理
     *
     * @param base
     */
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        LogUtil.D("loadDex     App attachBaseContext");
        if (!quickStart() && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {//>=5.0的系统默认对dex进行oat优化
            if (needWait(base)) {
                waitForDexopt(base);
            }
            MultiDex.install(this);
        } else {
            return;
        }
    }


    //-----------------------------------------进行分包加载---------------------------
    public static final String KEY_DEX2_SHA1 = "dex2-SHA1-Digest";

    public boolean quickStart() {
        if (getCurProcessName(this) != null && getCurProcessName(this).contains(":mini")) {  //StringUtils.contains(getCurProcessName(this), ":mini")
            LogUtil.D("loadDex  :mini start!");
            return true;
        }
        return false;
    }

    //neead wait for dexopt ?
    private boolean needWait(Context context) {
        String flag = get2thDexSHA1(context);
        LogUtil.D("loadDexdex2-sha1 " + flag);
        SharedPreferences sp = context.getSharedPreferences(
                PackageUtil.getPackageInfo(context).versionName, MODE_MULTI_PROCESS);
        String saveValue = sp.getString(KEY_DEX2_SHA1, "");
        return !saveValue.equals(flag);//!StringUtils.equals(flag,saveValue);
    }

    /**
     * Get classes.dex file signature
     *
     * @param context
     * @return
     */
    private String get2thDexSHA1(Context context) {
        ApplicationInfo ai = context.getApplicationInfo();
        String source = ai.sourceDir;
        try {
            JarFile jar = new JarFile(source);
            Manifest mf = jar.getManifest();
            Map<String, Attributes> map = mf.getEntries();
            Attributes a = map.get("classes2.dex");
            return a.getValue("SHA1-Digest");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    // optDex finish
    public void installFinish(Context context) {
        SharedPreferences sp = context.getSharedPreferences(
                PackageUtil.getPackageInfo(context).versionName, MODE_MULTI_PROCESS);
        sp.edit().putString(KEY_DEX2_SHA1, get2thDexSHA1(context)).commit();
    }

    public static String getCurProcessName(Context context) {
        try {
            int pid = android.os.Process.myPid();
            ActivityManager mActivityManager = (ActivityManager) context
                    .getSystemService(Context.ACTIVITY_SERVICE);
            for (ActivityManager.RunningAppProcessInfo appProcess : mActivityManager
                    .getRunningAppProcesses()) {
                if (appProcess.pid == pid) {
                    return appProcess.processName;
                }
            }
        } catch (Exception e) {
            // ignore
        }
        return null;
    }

    public void waitForDexopt(Context base) {
        Intent intent = new Intent();
        ComponentName componentName = new
                ComponentName("xxx.LoadResActivity"); //等待界面Activity的绝对路径
        intent.setComponent(componentName);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        base.startActivity(intent);
        long startWait = System.currentTimeMillis();
        long waitTime = 10 * 1000;
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB_MR1) {
            waitTime = 20 * 1000;//实测发现某些场景下有些2.3版本有可能10s都不能完成optdex
        }
        while (needWait(base)) {
            try {
                long nowWait = System.currentTimeMillis() - startWait;
                LogUtil.D("loadDexwait ms :" + nowWait+"    "+System.currentTimeMillis());

/*                SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
                String sd = sdf.format(new Date(System.currentTimeMillis()));
                LogUtil.D("loadDexwait ms--->     "+sd.toString());*/
                if (nowWait >= waitTime) {
                    return;
                }
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

我们对上述代码进行简单的分析:

  1. 我们选择的是重写attachBaseContext()方法,如果我们像官方给出的建议那么来写的话,在很多机型上边因为解析时间过长导致ANR,所以我们在这里进行了判断,
    1.1 首先判断系统版本,如果是5.0以上直接略过,因为系统默认对dex进行oat优化
    1.2 quickStart() 因为attachBaseContext()是在主线程中进行的,长时间加载会导致ANR,我们想要避免这种情况,就需要启动一个子进程,在其中完成dexopt的工作,这里是判断我们的子进程是否已经存在。

  2. 当子进程没有启动且系统版本小于5.0的时候,我们还需要判断是都已经进行过dexopt,这里我们用needWait()方法来进行判断,根据我们在sp中存储的classes2.dex的SHA1值来进行判断,在子进程中会有对应的操作。
  3. 如果没有进行过dexopt,此时会调用waitForDexopt()方法,启动子进程,并且阻塞主进程,不断调用needWait()方法进行轮询。


  • LoadResActivity
/**
 * creatime 2016年1月6日 18:33:57
 * @description 用于展示加载dex的activity
 */
public class LoadResActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        super .onCreate(savedInstanceState);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        overridePendingTransition(R.anim.null_anim, R.anim.null_anim);
        setContentView(R.layout.load_res_act);
        new LoadDexTask().execute();
    }
    class LoadDexTask extends AsyncTask {
        @Override
        protected Object doInBackground(Object[] params) {
            try {
                MultiDex.install(getApplication());
                LogUtils.d("loadDexinstall finish");
                ((MyApplication) getApplication()).installFinish(getApplication());
            } catch (Exception e) {
                LogUtil.D("loadDexe.getLocalizedMessage()");
            }
            return null;
        }
        @Override
        protected void onPostExecute(Object o) {
            LogUtils.d("loadDexget install finish");
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            finish();
                        }
                    });
                }
            }).start();
            finish();
        }
    }
    @Override
    public void onBackPressed() {
        //cannot backpress
    }
}

上述代码是子进程的代码,这里是显示给用户的等待界面,由于在另一个进程中,且在异步任务中进行的MultiDex.install(getApplication());操作,所以不会出现ANR,当install执行完成之后,调用Application中的installFinish()方法在sp中存储对应的值,并finish自身。主进程的下一次轮询之后会结束阻塞,并且再次调用MultiDex.install(this);
这里解释一下,再次调用该方法时,之前如果已经执行过,那么此次执行将非常快,耗时几乎可以忽略,二次执行的目的是为了确保install方法确实已经执行完毕,防止子进程中一些不可预知的原因而造成的失败。

onBackPressed()方法,需要重写,阻值用户在MultiDex.install(getApplication())操作时按返回键导致失败。

通过上述代码,我们可以解决绝大部分的不兼容问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值