您的位置:首页 > 移动开发 > Android开发

【Android】Google Multidex使用方法

2016-06-16 17:10 417 查看
对于Android进行dex分包的原因以及策略,这里就不再进行详细的叙述,具体的可以参考这篇文章,是我转载自美团技术分享的文章:

/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())操作时按返回键导致失败。

通过上述代码,我们可以解决绝大部分的不兼容问题。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: