【Android】Google Multidex使用方法
2016-06-16 17:10
417 查看
对于Android进行dex分包的原因以及策略,这里就不再进行详细的叙述,具体的可以参考这篇文章,是我转载自美团技术分享的文章:
/article/11895935.html
文章中,美团的大神已经将几种不同策略的优缺点说的非常的详尽,然后对于普通的小公司而言,并没有太多的人员精力来投入到其中,综合来说,使用google的MultiDex还是一个非常好的解决方法的。
切入正题,以Android studio为例,要使用MultiDex,分为以下几步:
首先,修改Gradle配置文件,启用MultiDex并包含MultiDex支持:
然后,让应用支持多DEX文件。在官方文档中描述了三种可选方法:
在AndroidManifest.xml的application中声明android.support.MultiDex.MultiDexApplication;
如果你已经有自己的Application类,让其继承MultiDexApplication;
如果你的Application类已经继承自其它类,你不想/能修改它,那么可以重写attachBaseContext()方法:
如果没有在manifest中对application进行声明的话,还需要对application添加声明:
此时MultiDex已经集成完毕。可以运行起来,但在实际中,你会发现在许多低端机型上边会出现各种各样的问题,例如INSTALL_FAILED_DEXOPT、ANR等问题,具体原因参见这里:
http://www.52pojie.cn/thread-435851-1-1.html
解决方法如下:
build.gradle中进行配置(注意,这里的配置仅适用于gradle1.5 以下,后边gradle语法进行了更新,请自行查询)
上述代码限制了每个dex包的大小,解决了部分机型INSTALL_FAILED_DEXOPT的问题
Application中代码
我们对上述代码进行简单的分析:
我们选择的是重写attachBaseContext()方法,如果我们像官方给出的建议那么来写的话,在很多机型上边因为解析时间过长导致ANR,所以我们在这里进行了判断,
1.1 首先判断系统版本,如果是5.0以上直接略过,因为系统默认对dex进行oat优化
1.2 quickStart() 因为attachBaseContext()是在主线程中进行的,长时间加载会导致ANR,我们想要避免这种情况,就需要启动一个子进程,在其中完成dexopt的工作,这里是判断我们的子进程是否已经存在。
当子进程没有启动且系统版本小于5.0的时候,我们还需要判断是都已经进行过dexopt,这里我们用needWait()方法来进行判断,根据我们在sp中存储的classes2.dex的SHA1值来进行判断,在子进程中会有对应的操作。
如果没有进行过dexopt,此时会调用waitForDexopt()方法,启动子进程,并且阻塞主进程,不断调用needWait()方法进行轮询。
LoadResActivity
上述代码是子进程的代码,这里是显示给用户的等待界面,由于在另一个进程中,且在异步任务中进行的MultiDex.install(getApplication());操作,所以不会出现ANR,当install执行完成之后,调用Application中的installFinish()方法在sp中存储对应的值,并finish自身。主进程的下一次轮询之后会结束阻塞,并且再次调用MultiDex.install(this);
这里解释一下,再次调用该方法时,之前如果已经执行过,那么此次执行将非常快,耗时几乎可以忽略,二次执行的目的是为了确保install方法确实已经执行完毕,防止子进程中一些不可预知的原因而造成的失败。
onBackPressed()方法,需要重写,阻值用户在MultiDex.install(getApplication())操作时按返回键导致失败。
通过上述代码,我们可以解决绝大部分的不兼容问题。
/article/11895935.html
文章中,美团的大神已经将几种不同策略的优缺点说的非常的详尽,然后对于普通的小公司而言,并没有太多的人员精力来投入到其中,综合来说,使用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文件。在官方文档中描述了三种可选方法:
在AndroidManifest.xml的application中声明android.support.MultiDex.MultiDexApplication;
如果你已经有自己的Application类,让其继承MultiDexApplication;
如果你的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(); } } }
我们对上述代码进行简单的分析:
我们选择的是重写attachBaseContext()方法,如果我们像官方给出的建议那么来写的话,在很多机型上边因为解析时间过长导致ANR,所以我们在这里进行了判断,
1.1 首先判断系统版本,如果是5.0以上直接略过,因为系统默认对dex进行oat优化
1.2 quickStart() 因为attachBaseContext()是在主线程中进行的,长时间加载会导致ANR,我们想要避免这种情况,就需要启动一个子进程,在其中完成dexopt的工作,这里是判断我们的子进程是否已经存在。
当子进程没有启动且系统版本小于5.0的时候,我们还需要判断是都已经进行过dexopt,这里我们用needWait()方法来进行判断,根据我们在sp中存储的classes2.dex的SHA1值来进行判断,在子进程中会有对应的操作。
如果没有进行过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())操作时按返回键导致失败。
通过上述代码,我们可以解决绝大部分的不兼容问题。
相关文章推荐
- 关于Android的.so文件你所需要知道的
- Android ViewPager
- 关于ExpandableListView展开滑动的问题
- Android入门教程 自定义View详解 真实案例
- RxJava简介及在androidstudio中引入RxAndroid
- Android官方提供的支持不同屏幕大小的全部方法
- Pro Android学习笔记(一一四):2D动画(9):Property Animation(下)
- [Android官方Demo系列] View滑动
- androidNDK
- Android 使用ORMLITE 自定义规则的排序
- android 加密:Hmac消息认证作用
- AndroidManifest.xml文件解析
- Android中自定义圆角的Dialog
- android Recyclerview仿京东,滚动屏幕标题栏渐变
- gradle打包android (实现外部导入签名文件、多渠道打包、导入ant脚本)
- Android内存泄漏原因及解决方法
- 利用provider获取系统联系人
- android material design之Tablayout,Recyclerview,Fragment,Viewpager搭配使用(四)
- Android自定义日期选择器
- 通过dexdump来学习DEX文件格式