Android中方法数超限问题与启动优化详解

Android中方法数超限问题与启动优化详解,第1张

概述前言最近写了篇有关Eclipse工程转AndroidStudio工程的文章,而导致公司项目需要转AS的直接原因,就是今天要写的主题C方法数超限,相信大多数Android项目的都会碰到这个问题。

前言

最近写了篇有关Eclipse工程转Android Studio工程的文章,而导致公司项目需要转 AS 的直接原因,就是今天要写的主题C方法数超限,相信大多数 AndroID 项目的都会碰到这个问题。

传统的 Eclipse 解决方法数超限的办法,就是在 project.propertIEs 中加上 dex.force.jumbo=true ,然后清理工程重新编译。但是,当方法数越来越多,这个方法也会解决不了问题,这个时候,就要用到 Google 官方给出的方案 MultiDex 了。

MultIDex 解决方案

一、 如果你的 minSdkVersion >= 21  ,那么只要在模块的 build.gradle 中添加:

androID { defaultConfig {  ...  multIDexEnabled true } ...}

二、 如果你的 minSdkVersion < 21  ,那么只要在模块的 build.gradle 中添加:

androID { defaultConfig {  ...  multIDexEnabled true } ...}dependencIEs { compile 'com.androID.support:multIDex:1.0.1'}

然后,如果你没有写自己的 Application 类,那么你只要写上自己的 Application 类,并继承 MultIDexApplication ;如果你写过自己的 Application 类,并且/或者不希望 Application 类继承 MultIDexApplication ,那么你需要重写 Application 的 attachBaseContext 方法:

@OverrIDeprotected voID attachBaseContext(Context base) { super.attachBaseContext(context); MultIDex.install(this);}

谷歌multIDex存在的问题

虽然谷歌的分包方案很简单,但是效果并不是那么好,谷歌本身也枚举了分包方案的缺点:

如果在主线程中执行MultIDex.install,加载second dex,因为加载从dex是同步的,会阻塞线程,second dex太大的话,有可能导致ANR API Level 14之前,由于Dalvik linearalloc BUG(问题22586,就是上文提到的linearalloc问题),很可能会出问题的 应用程序使用了multIEdex配置的,会造成使用比较大的内存 对于应用程序比较复杂的,存在较多的library的项目。multIDex可能会造成不同依赖项目间的dex文件函数相互调用,找不到方法

启动优化

官方的解决方案虽然简单,但是也存在一定的局限。比如,首次加载应用时,由于需要加载 DEX 文件,会消耗较多的时间,导致启动速度慢,影响用户体验,甚至很可能引发 ANR 。

针对加载 Dex 问题,美团技术团队是这样做的:精简主 Dex 包,应用启动起来后再异步加载第二个 Dex 包。这是一个很不错的想法,但是实现起来有一定的难度。需要编写脚本,区分哪些类要放在主 Dex 包中,而且一般项目中都会用到很多第三方 SDK,这很可能导致主 Dex 包的精简程度不能达到我们想要的状态。

当然,除此之外,还有更适合我们的方案,微信开发团队的解决思路就很有意思,他们逆了不少 APP,最后参考并改进了 Facebook 的解决方案。大概的思路就是,新开一个进程来执行 MultIDex.install() 方法。

微信开发团队的思路实现起来也比较简单,下面直接上我的代码(顺便把启动体验也优化了~):

Application 中的 attachBaseContext 方法:

@OverrIDeprotected voID attachBaseContext(Context context) { super.attachBaseContext(context); if (MultIDexUtils.isMainProcess(context)) { // 判断是否是主进程,避免重复执行  MultIDexUtils.setMainActivityStarted(this,false); // 保存本地数据,标记主页面是否已经开启  MultIDexUtils.setLoadDexActivityClosed(this,false); // 保存本地数据,标记加载Dex进程是否已经关闭  MultIDexUtils.startLoadDexActivity(context); // 打开加载 Dex 的进程页面,这样我们的APP就变成后台进程了 }}

加载 Dex 的进程:

public class LoadDexActivity extends Activity { private static final int MulTIDEX_ERROR = 0; private static final int MulTIDEX_ACTIVITY_STARTED = 1; Handler handler = new Handler() {  @OverrIDe  public voID handleMessage(Message msg) {   switch (msg.what) {    case MulTIDEX_ERROR:    case MulTIDEX_ACTIVITY_STARTED: // 退出当前进程     MultIDexUtils.setLoadDexActivityClosed(getApplication());     finish();     System.exit(0);     break;    default:     break;   }  } }; @OverrIDe protected voID onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState);  setContentVIEw(R.layout.activity_loaddex);  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LolliPOP) {   getwindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);  }  new Thread() {   @OverrIDe   public voID run() {    Message message = handler.obtainMessage();    long startTime = System.currentTimeMillis();    long timeout = 10 * 1000; // 加载超时时间    try {     MultIDex.install(getApplication());     Thread.sleep(500);     // 等待主界面启动     while (!MultIDexUtils.isMainActivityStarted(getApplication()) &&       (System.currentTimeMillis() - startTime) < timeout) {      Thread.sleep(200);     }     message.what = MulTIDEX_ACTIVITY_STARTED;     handler.sendMessage(message);    } catch (Exception e) {     message.what = MulTIDEX_ERROR;     handler.sendMessage(message);    }   }  }.start(); } @OverrIDe public voID onBackpressed() {  //cannot backpress }}

Manifest 中 LoadDexActivity 的配置:

<activity androID:name=".LoadDexActivity" androID:alwaysRetainTaskState="false" androID:excludeFromrecents="true" androID:launchMode="singleTask" androID:process=":loadDex" androID:screenorIEntation="portrait"></activity>

主界面 onCreate 方法:

@OverrIDepublic voID onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... MultIDexUtils.setMainActivityStarted(getApplication()); // 告诉loadDex进程,主界面已启动 ...}

MultIDexUtils 工具类:

public class MultIDexUtils { public static final String KEY_ACTIVITY_STARTED = "activity-started-"; public static final String KEY_LOADDEX_CLOSED = "loaddex-closed-"; /**  * 当前进程是否是主进程  */ public static boolean isMainProcess(Context context) {  return "com.***.***(进程名一般是包名)".equals(getCurProcessname(context)); } /**  * 设置-主界面已经打开  */ public static voID setMainActivityStarted(Context context) {  setMainActivityStarted(context,true); } /**  * 设置-主界面是否已经打开  */ public static voID setMainActivityStarted(Context context,boolean b) {  SharedPreferences sp = context.getSharedPreferences("multIDex",Context.MODE_MulTI_PROCESS);  sp.edit().putBoolean(KEY_ACTIVITY_STARTED + getPackageInfo(context).versionCode,b).commit(); } /**  * 是否需要等待主界面  */ public static boolean isMainActivityStarted(Context context) {  SharedPreferences sp = context.getSharedPreferences("multIDex",Context.MODE_MulTI_PROCESS);  return sp.getBoolean(KEY_ACTIVITY_STARTED + getPackageInfo(context).versionCode,false); } /**  * 判断加载页面是否关闭  */ public static boolean isLoadDexActivityClosed(Context context) {  SharedPreferences sp = context.getSharedPreferences("multIDex",Context.MODE_MulTI_PROCESS);  return sp.getBoolean(KEY_LOADDEX_CLOSED + getPackageInfo(context).versionCode,false); } /**  * 设置加载页面已经关闭  */ public static voID setLoadDexActivityClosed(Context context) {  setLoadDexActivityClosed(context,true); } /**  * 设置-加载页面是否已经关闭  */ public static voID setLoadDexActivityClosed(Context context,Context.MODE_MulTI_PROCESS);  sp.edit().putBoolean(KEY_LOADDEX_CLOSED + getPackageInfo(context).versionCode,b).commit(); } /**  * 开启等待页面,新的进程  */ public static voID startLoadDexActivity(Context context) {  Intent intent = new Intent();  Componentname componentname = new Componentname("com.***.***(包名)",LoadDexActivity.class.getname());  intent.setComponent(componentname);  intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  context.startActivity(intent); } /**  * 获取进程名  */ 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; } /**  * 获取包信息  */ private static PackageInfo getPackageInfo(Context context) {  PackageManager pm = context.getPackageManager();  try {   return pm.getPackageInfo(context.getPackagename(),0);  } catch (PackageManager.nameNotFoundException e) {//   Log.i(TAG,e.getLocalizedMessage());  }  return new PackageInfo(); }}

另一种启动优化方案

还有一种简单的启动优化方案,只能优化启动体验,并不能解决 ANR 问题。

在点击桌面图标启动应用时,给个背景图片,启动完成后,将背景设回空。

1.在入口 Activity 中加入主题背景

androID:theme="@style/Splashtheme"

style.xml 中加入配置:

value:

<style name="Splashtheme" parent="@androID:style/theme.NoTitlebar"> <item name="androID:background">@drawable/logo_splash</item></style>

value-v21:(解决21版本及以上的过度动画效果问题)

<style name="Splashtheme" parent="@androID:style/theme.NoTitlebar.Fullscreen"> <item name="androID:windowBackground">@drawable/logo_splash</item></style>

2.将背景设回原样

@OverrIDepublic voID settheme(int resID) { super.settheme(R.style.Customtransparent);}

style.xml 配置如下:

<style name="Customtransparent" parent="@androID:style/theme.Translucent"> <item name="androID:background">@null</item> <item name="androID:windowBackground">@color/curve_floater_framecolor</item></style>

参考

其实你不知道 MultiDex 到底有多坑 美团 Android DEX 自动拆包及动态加载简介 Android 拆分与加载 Dex 的多种方案对比 配置方法数超过 64K 的应用

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对编程小技巧的支持。

总结

以上是内存溢出为你收集整理的Android中方法数超限问题与启动优化详解全部内容,希望文章能够帮你解决Android中方法数超限问题与启动优化详解所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1143561.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-31
下一篇 2022-05-31

发表评论

登录后才能评论

评论列表(0条)

保存