一、MultiDex出现原因:
当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上的项目样例。
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或以上且配置一般的机子都能正常运行,其他的特殊例子这里不做研究了。