对于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文件。在官方文档中描述了三种可选方法:
- 在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应用因方法数超过65536限制时采用Google MultiDex进行dex分包的解决方案。内容包括启用MultiDex的Gradle配置,AndroidManifest.xml的应用声明,以及解决低端机型可能出现的INSTALL_FAILED_DEXOPT、ANR等问题的方法,包括子进程加载dex优化策略。

1853

被折叠的 条评论
为什么被折叠?



