Android MultiDex使用

一、MultiDex出现原因:

下段摘自http://www.cnblogs.com/CharlesGrant/p/5112597.html

当Android系统安装一个应用的时候,有一步是对Dex进行优化,这个过程有一个专门的工具来处理,叫DexOpt。DexOpt的执行过程是在第一次加载Dex文件的时候执行的。这个过程会生成一个ODEX文件,即Optimised Dex。执行ODex的效率会比直接执行Dex文件的效率要高很多。

但是在早期的Android系统中,DexOpt有一个问题,DexOpt会把每一个类的方法id检索起来,存在一个链表结构里面。但是这个链表的长度是用一个short类型来保存的,导致了方法id的数目不能够超过65536个。当一个项目足够大的时候,显然这个方法数的上限是不够的。尽管在新版本的Android系统中,DexOpt修复了这个问题,但是我们仍然需要对低版本的Android系统做兼容。

为了解决方法数超限的问题,需要将该dex文件拆成两个或多个,为此谷歌官方推出了multidex兼容包,配合AndroidStudio实现了一个APK包含多个dex的功能。

目前在已经在API 21中提供了通用的解决方案,那就是android-support-multidex.jar. 这个jar包最低可以支持到API 4的版本(Android L及以上版本会默认支持mutidex).

引起的错误:

Android方法数不能超过65K的限制:

Conversion to Dalvik format failed:Unable toexecute dex: method ID not in [0, 0xffff]: 65536

可能有些同学会说,解决这个问题很简单,我们只需要在Project.proterty中配置一句话就Ok啦,

dex.force.jumbo=true

是的,加入了这句话,确实可以让你的应用通过编译,但是在一些2.3系统的机器上很容易出现

INSTALL_FAILED_DEXOPT异常

对于以上两个异常,我们先来分析一下原因:

1、Android系统中,一个Dex文件中存储方法id用的是short类型数据,所以导致你的dex中方法不能超过65k

2、在2.3系统之前,虚拟机内存只分配了5M

使用:

multidex是一个文档齐全的成熟的解决方案。强烈推荐遵循安卓开发者网站上的指示来启用multidex。也可以参考github上的项目样例

在app/build.gradle 下,添加:(记得 multiDexEnabled true

1 compile 'com.android.support:multidex:1.0.1'

在application中:(没有继承其他application的,使用MultidexApplication,如果继承了其他application的,就用如下方式加载)

1 @Override
2     protected void attachBaseContext(Context base) {
3         super.attachBaseContext(base);
4         //因为引用的包过多,实现多包问题
5         MultiDex.install(this);
6     }

二、安卓5.0及以上谷歌已经可以做好了处理,但是5.0以下的可能就会有问题,接下来看看5.0以下的实现:

1.TestApplication继承自Application,优化的时候先判断是否是5.0一下,是的话去去看是否该项目有class2.dex,如果有我们就等待dexopt,当等待时间结束,我们用一布尔值来标记

public class TestApplication extends Application

{
    private static TestApplication instance;

    @Override
    public void onCreate()
    {
        super.onCreate();
        instance=this;
     }
     
     public static TestApplication getInstance() {
        return instance;
    }

    

    /**
     * Dex尝试
     * @param
     */
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        isDexOk = false;
        //只有主进程以及SDK版本5.0以下才走。
        if (isMainProcess(AppContext.this) && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            if (needWait(base)){
                waitForDexopt(base,3000);  //5.0一下且有class2.dex,等待Dexopt,第二个参数设定等待时间3秒
            }
            isDexOk = true;
            MultiDex.install (this);
            LogUtil.log("loadDex","主进程或安卓5.0以下");
        }else {
            isDexOk = true;
            LogUtil.log("loadDex","主进程或安卓5.0以上");
        }
    }

    public static final String KEY_DEX2_SHA1 = "dex2-SHA1-Digest";
    //neead wait for dexopt ?
    private boolean needWait(Context context){
        String flag = get2thDexSHA1(context);
        LogUtil.log( "loadDex", "dex2-sha1 "+flag);
        if(TextUtils.isEmpty(flag)){
            return false;  //证明根本没有超过一个dex包
        }
        SharedPreferences sp = context.getSharedPreferences(getPackageInfo(context). versionName, MODE_MULTI_PROCESS);
        String saveValue = sp.getString(KEY_DEX2_SHA1, "");
        return !flag.equals(saveValue);  //TODO 有多分包的就比较启动等待

//        boolean wait = !flag.equals(saveValue);
//        return !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(getPackageInfo(context).versionName, MODE_MULTI_PROCESS);
        sp.edit().putString(KEY_DEX2_SHA1,get2thDexSHA1(context)).commit();
    }

    public void waitForDexopt(Context base) {
       /* Intent intent = new Intent(base,SplashActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        base.startActivity(intent);*/
        long startWait = System.currentTimeMillis ();
        long waitTime = 3000 ;
        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.log("loadDex" , "wait ms :" + nowWait);
                if (nowWait >= waitTime) {
                    return;
                }
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    public PackageInfo getPackageInfo(Context context){
        PackageManager pm = context.getPackageManager();
        try {
            return pm.getPackageInfo(context.getPackageName(), 0);
        } catch (PackageManager.NameNotFoundException e) {
            LogUtil.log("loadDex",e.getLocalizedMessage());
        }
        return  new PackageInfo();
    }

    private boolean isDexOk;
    public boolean getDexOk(){
        return isDexOk;
    }

    public static boolean isMainProcess(Context context) {
        return context.getPackageName().equals(getProcessName(context));
    }

    /**
     * 获取进程名称
     *
     * @param context
     * @return
     */
    public static String getProcessName(Context context) {
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> runningApps = am.getRunningAppProcesses();
        if (runningApps == null) {
            return null;
        }
        for (ActivityManager.RunningAppProcessInfo proInfo : runningApps) {
            if (proInfo.pid == android.os.Process.myPid()) {
                if (proInfo.processName != null) {
                    return proInfo.processName;
                }
            }
        }
        return null;
    }
}

2.一般进入App都会有闪屏,姑且叫SplashActivity,我们在闪屏处也进行处理,在启动的时候都去MultiDex.install(getApplication());



public class SplashActivity extends Activity {
    public static final String TAG  = SplashActivity.class.getSimpleName();


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash);
        LoadDexTask loadDexTask = new LoadDexTask();
        loadDexTask.execute();
        count = 0;
        runnable.run();  //用线程来看
    }


     int count = 0;
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            if( AppContext.getInstance().getDexOk() && isFinish){
                LogUtil.log("loadDex","count1 = "+count);
                jumpToActivity();
            }else {
                count++;
                LogUtil.log("loadDex","count2 = "+count);
                handler.postDelayed(this,200);
            }
        }
    };


    Handler handler = new Handler();


    private void jumpToActivity(){
        Intent intent = new Intent(SplashActivity.this, WelcomeActivity.class);
        startActivity(intent);
        finish();
    }


    boolean isFinish = false;   //异步线程优化
    class LoadDexTask extends AsyncTask {
        @Override
        protected Object doInBackground(Object[] params) {
            isFinish = false;
            try {
                MultiDex.install(getApplication());
                LogUtil.log("loadDex" , "install finish" );
                AppContext.getInstance().installFinish(getApplication());
            } catch (Exception e) {
                LogUtil.log("loadDex" , e.getLocalizedMessage());
            }
            return null;
        }
        @Override
        protected void onPostExecute(Object o) {
            LogUtil.log( "loadDex", "get install finish");
            isFinish = true;
        }
    }
}

用异步线程来控制,主要是在TestApplication中的等待Dexopt中是否完成,当项目大的时候3秒很有可能是完成不了的,我这里定死了,其实可以用循环来处理,隔几百毫秒去去查看一次,当保证都优化完了标志为true再跳转,在API16或以上且配置一般的机子都能正常运行,其他的特殊例子这里不做研究了。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值