Android中方法数超限问题与启动优化详解
前言
最近写了篇有关Eclipse工程转AndroidStudio工程的文章,而导致公司项目需要转AS的直接原因,就是今天要写的主题–方法数超限,相信大多数Android项目的都会碰到这个问题。
传统的Eclipse解决方法数超限的办法,就是在project.properties中加上dex.force.jumbo=true,然后清理工程重新编译。但是,当方法数越来越多,这个方法也会解决不了问题,这个时候,就要用到Google官方给出的方案MultiDex了。
MultiDex解决方案
一、如果你的minSdkVersion>=21 ,那么只要在模块的build.gradle中添加:
android{
defaultConfig{
...
multiDexEnabledtrue
}
...
}
二、如果你的minSdkVersion<21 ,那么只要在模块的build.gradle中添加:
android{
defaultConfig{
...
multiDexEnabledtrue
}
...
}
dependencies{
compile'com.android.support:multidex:1.0.1'
}
然后,如果你没有写自己的Application类,那么你只要写上自己的Application类,并继承MultiDexApplication;如果你写过自己的Application类,并且/或者不希望Application类继承MultiDexApplication,那么你需要重写Application的attachBaseContext方法:
@Override
protectedvoidattachBaseContext(Contextbase){
super.attachBaseContext(context);
Multidex.install(this);
}
谷歌multiDex存在的问题
虽然谷歌的分包方案很简单,但是效果并不是那么好,谷歌本身也枚举了分包方案的缺点:
- 如果在主线程中执行MultiDex.install,加载seconddex,因为加载从dex是同步的,会阻塞线程,seconddex太大的话,有可能导致ANR
- APILevel14之前,由于DalvikLinearAllocbug(问题22586,就是上文提到的LinearAlloc问题),很可能会出问题的
- 应用程序使用了multiedex配置的,会造成使用比较大的内存
- 对于应用程序比较复杂的,存在较多的library的项目。multidex可能会造成不同依赖项目间的dex文件函数相互调用,找不到方法
启动优化
官方的解决方案虽然简单,但是也存在一定的局限。比如,首次加载应用时,由于需要加载DEX文件,会消耗较多的时间,导致启动速度慢,影响用户体验,甚至很可能引发ANR。
针对加载Dex问题,美团技术团队是这样做的:精简主Dex包,应用启动起来后再异步加载第二个Dex包。这是一个很不错的想法,但是实现起来有一定的难度。需要编写脚本,区分哪些类要放在主Dex包中,而且一般项目中都会用到很多第三方SDK,这很可能导致主Dex包的精简程度不能达到我们想要的状态。
当然,除此之外,还有更适合我们的方案,微信开发团队的解决思路就很有意思,他们逆了不少APP,最后参考并改进了Facebook的解决方案。大概的思路就是,新开一个进程来执行Multidex.install()方法。
微信开发团队的思路实现起来也比较简单,下面直接上我的代码(顺便把启动体验也优化了~):
Application中的attachBaseContext方法:
@Override
protectedvoidattachBaseContext(Contextcontext){
super.attachBaseContext(context);
if(MultiDexUtils.isMainProcess(context)){//判断是否是主进程,避免重复执行
MultiDexUtils.setMainActivityStarted(this,false);//保存本地数据,标记主页面是否已经开启
MultiDexUtils.setLoadDexActivityClosed(this,false);//保存本地数据,标记加载Dex进程是否已经关闭
MultiDexUtils.startLoadDexActivity(context);//打开加载Dex的进程页面,这样我们的APP就变成后台进程了
}
}
加载Dex的进程:
publicclassLoadDexActivityextendsActivity{
privatestaticfinalintMULTIDEX_ERROR=0;
privatestaticfinalintMULTIDEX_ACTIVITY_STARTED=1;
Handlerhandler=newHandler(){
@Override
publicvoidhandleMessage(Messagemsg){
switch(msg.what){
caseMULTIDEX_ERROR:
caseMULTIDEX_ACTIVITY_STARTED://退出当前进程
MultiDexUtils.setLoadDexActivityClosed(getApplication());
finish();
System.exit(0);
break;
default:
break;
}
}
};
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_loaddex);
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.LOLLIPOP){
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
newThread(){
@Override
publicvoidrun(){
Messagemessage=handler.obtainMessage();
longstartTime=System.currentTimeMillis();
longtimeout=10*1000;//加载超时时间
try{
MultiDex.install(getApplication());
Thread.sleep(500);
//等待主界面启动
while(!MultiDexUtils.isMainActivityStarted(getApplication())&&
(System.currentTimeMillis()-startTime)
Manifest中LoadDexActivity的配置:
主界面onCreate方法:
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
...
MultiDexUtils.setMainActivityStarted(getApplication());//告诉loadDex进程,主界面已启动
...
}
MultiDexUtils工具类:
publicclassMultiDexUtils{
publicstaticfinalStringKEY_ACTIVITY_STARTED="activity-started-";
publicstaticfinalStringKEY_LOADDEX_CLOSED="loaddex-closed-";
/**
*当前进程是否是主进程
*/
publicstaticbooleanisMainProcess(Contextcontext){
return"com.***.***(进程名一般是包名)".equals(getCurProcessName(context));
}
/**
*设置-主界面已经打开
*/
publicstaticvoidsetMainActivityStarted(Contextcontext){
setMainActivityStarted(context,true);
}
/**
*设置-主界面是否已经打开
*/
publicstaticvoidsetMainActivityStarted(Contextcontext,booleanb){
SharedPreferencessp=context.getSharedPreferences("multidex",Context.MODE_MULTI_PROCESS);
sp.edit().putBoolean(KEY_ACTIVITY_STARTED+getPackageInfo(context).versionCode,b).commit();
}
/**
*是否需要等待主界面
*/
publicstaticbooleanisMainActivityStarted(Contextcontext){
SharedPreferencessp=context.getSharedPreferences("multidex",Context.MODE_MULTI_PROCESS);
returnsp.getBoolean(KEY_ACTIVITY_STARTED+getPackageInfo(context).versionCode,false);
}
/**
*判断加载页面是否关闭
*/
publicstaticbooleanisLoadDexActivityClosed(Contextcontext){
SharedPreferencessp=context.getSharedPreferences("multidex",Context.MODE_MULTI_PROCESS);
returnsp.getBoolean(KEY_LOADDEX_CLOSED+getPackageInfo(context).versionCode,false);
}
/**
*设置加载页面已经关闭
*/
publicstaticvoidsetLoadDexActivityClosed(Contextcontext){
setLoadDexActivityClosed(context,true);
}
/**
*设置-加载页面是否已经关闭
*/
publicstaticvoidsetLoadDexActivityClosed(Contextcontext,booleanb){
SharedPreferencessp=context.getSharedPreferences("multidex",Context.MODE_MULTI_PROCESS);
sp.edit().putBoolean(KEY_LOADDEX_CLOSED+getPackageInfo(context).versionCode,b).commit();
}
/**
*开启等待页面,新的进程
*/
publicstaticvoidstartLoadDexActivity(Contextcontext){
Intentintent=newIntent();
ComponentNamecomponentName=newComponentName("com.***.***(包名)",LoadDexActivity.class.getName());
intent.setComponent(componentName);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
/**
*获取进程名
*/
publicstaticStringgetCurProcessName(Contextcontext){
try{
intpid=android.os.Process.myPid();
ActivityManagermActivityManager=(ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
for(ActivityManager.RunningAppProcessInfoappProcess:mActivityManager.getRunningAppProcesses()){
if(appProcess.pid==pid){
returnappProcess.processName;
}
}
}catch(Exceptione){
//ignore
}
returnnull;
}
/**
*获取包信息
*/
privatestaticPackageInfogetPackageInfo(Contextcontext){
PackageManagerpm=context.getPackageManager();
try{
returnpm.getPackageInfo(context.getPackageName(),0);
}catch(PackageManager.NameNotFoundExceptione){
//Log.i(TAG,e.getLocalizedMessage());
}
returnnewPackageInfo();
}
}
另一种启动优化方案
还有一种简单的启动优化方案,只能优化启动体验,并不能解决ANR问题。
在点击桌面图标启动应用时,给个背景图片,启动完成后,将背景设回空。
1.在入口Activity中加入主题背景
android:theme="@style/SplashTheme"
style.xml中加入配置:
value:
@drawable/logo_splash
value-v21:(解决21版本及以上的过度动画效果问题)
@drawable/logo_splash
2.将背景设回原样
@Override
publicvoidsetTheme(intresid){
super.setTheme(R.style.CustomTransparent);
}
style.xml配置如下:
@null
@color/curve_floater_frameColor
参考
- 其实你不知道MultiDex到底有多坑
- 美团AndroidDEX自动拆包及动态加载简介
- Android拆分与加载Dex的多种方案对比
- 配置方法数超过64K的应用
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。