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

前言

最近写了篇有关Eclipse工程转Android Studio工程的文章,而导致公司项目需要转 AS 的直接原因,就是今天要写的主题–方法数超限,相信大多数 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 方法:

@Override
protected 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 方法:

@Override
protected 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 方法:

@Override
public 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, boolean b) {
  SharedPreferences sp = context.getSharedPreferences("multidex", 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.将背景设回原样

@Override
public 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>

参考

总结

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

您可能感兴趣的文章:

  • Android优化应用启动速度
  • Android应用启动速度优化
  • Android App应用启动分析与优化
  • Android优化之启动页去黑屏实现秒启动
  • Android APP启动方式、启动流程及启动优化分析
  • 浅谈android性能优化之启动过程(冷启动和热启动)
(0)

相关推荐

  • Android优化应用启动速度

    一.应用的启动 启动方式 通常来说,在安卓中应用的启动方式分为两种:冷启动和热启动. 1.冷启动:当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动. 2.热启动:当启动应用时,后台已有该应用的进程(例:按back键.home键,应用虽然会退出,但是该应用的进程是依然会保留在后台,可进入任务列表查看),所以在已有进程的情况下,这种启动会从已有的进程中来启动应用,这个方式叫热启动. 特点 1.冷启动:冷启动因为系统会重新创建一个新的进程分配给它,

  • Android App应用启动分析与优化

    app的启动方式:  1.)冷启动  当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动.冷启动因为系统会重新创建一个新的进程分配给它,所以会先创建和初始化Application类,再创建和初始化MainActivity类(包括一系列的测量.布局.绘制),最后显示在界面上.  2.)热启动  当启动应用时,后台已有该应用的进程(例:按back键.home键,应用虽然会退出,但是该应用的进程是依然会保留在后台,可进入任务列表查看),所以在已有进程

  • 浅谈android性能优化之启动过程(冷启动和热启动)

    本文介绍了浅谈android性能优化之启动过程(冷启动和热启动) ,分享给大家,具体如下: 一.应用的启动方式 通常来说,启动方式分为两种:冷启动和热启动. 1.冷启动:当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动. 2.热启动:当启动应用时,后台已有该应用的进程(例:按back键.home键,应用虽然会退出,但是该应用的进程是依然会保留在后台,可进入任务列表查看),所以在已有进程的情况下,这种启动会从已有的进程中来启动应用,这个方式叫热

  • Android应用启动速度优化

    开发Android应用中,随着功能越来越多,启动速度越来越慢.有没有办法让自己应用启动速度快一点呢? 方法是人想出来的.先说说我的实现方法: 1 将onCreate 中初始化的内容,移动到线程中做初始化,加载等 2 初始化完成之后,通过Handler发送消息, 3 Hander 中收到消息后,再初始化完整界面. public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedIn

  • Android APP启动方式、启动流程及启动优化分析

    本文章向大家介绍Android app应用启动的一些相关知识,包括app启动方式.app启动流程和app启动优化等知识!  app应用启动方式 1.冷启动 当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动.冷启动因为系统会重新创建一个新的进程分配给它,所以会先创建和初始化Application类,再创建和初始化MainActivity类(包括一系列的测量.布局.绘制),最后显示在界面上. 2.热启动 当启动应用时,后台已有该应用的进程(例:按

  • Android优化之启动页去黑屏实现秒启动

    前言 还记得之前我们写了一篇文章,基于RxJava实现酷炫启动页,然而当我们点击桌面图标启动APP时,有时会闪一下黑色背景,有时黑色背景时间还比较长,哎呀,难看死了,这个怎么办捏,别方,我们今天就来看看启动页的优化. 一.消除启动时的黑屏 点击桌面launcher图标启动APP,闪现的黑色背景其实是出现在我们看到界面第一帧之前.那我们就要想办法让这个黑色的背景变成用户喜欢看到的画面或者让它透明化.有了思路方法也就粗现了,我们有下面两种方案: 自定义WelcomActivity的Theme 说白了

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

    前言 最近写了篇有关Eclipse工程转Android Studio工程的文章,而导致公司项目需要转 AS 的直接原因,就是今天要写的主题–方法数超限,相信大多数 Android 项目的都会碰到这个问题. 传统的 Eclipse 解决方法数超限的办法,就是在 project.properties 中加上 dex.force.jumbo=true ,然后清理工程重新编译.但是,当方法数越来越多,这个方法也会解决不了问题,这个时候,就要用到 Google 官方给出的方案 MultiDex了. Mul

  • Android中Activity生命周期和启动模式详解

    Activity生命周期经典图解: 按键对生命周期的影响: BACK键: 当我们按BACK键时,我们这个应用程序将结束,这时候我们将先后调用onPause()->onStop()->onDestory()三个方法. 再次启动App时,会执行onCreate()->onStart()->onResume() HOME键: 当我们打开应用程序时,比如浏览器,我正在浏览NBA新闻,看到一半时,我突然想听歌,这时候我们会选择按HOME键,然后去打开音乐应用程序,而当我们按HOME的时候,A

  • 关于Android冷启动耗时优化详解

    目录 1,背景 2,调研 2.1,Android中启动的方式 2.2,冷启动流程 2.3,启动时间 3,方案 1,冷启动白屏现象 2,启动时间优化 总结 1,背景 最近开发了一个新的App,前期工期紧,做的比较粗放,上线以后发现App启动时间比较长,达到3秒, 启动有白屏,体验也不好,这个只能后期优化了,最好是前期开发就考虑的 2,调研 2.1,Android中启动的方式 1,冷启动:如果App启动时,后台没有该应用进程,那么系统会重新创建一个进程分配给该应用,这种启动方式就是冷启动 2,热启动

  • Android性能优化之弱网优化详解

    目录 弱网优化 1.Serializable原理 1.1 分析过程 1.2 Serializable接口 1.3 ObjectOutputStream 1.4 序列化后二进制文件的一点解读 1.5 常见的集合类的序列化问题 1.5.1 HashMap 1.5.2 ArrayList 2.Parcelable 2.1 Parcel的简介 2.2 Parcelable的三大过程介绍(序列化.反序列化.描述) 2.2.1 描述 2.2.2 序列化 2.2.3 反序列化 2.3 Parcelable的实

  • Android zygote启动流程详解

    对zygote的理解 在Android系统中,zygote是一个native进程,是所有应用进程的父进程.而zygote则是Linux系统用户空间的第一个进程--init进程,通过fork的方式创建并启动的. 作用 zygote进程在启动时,会创建一个Dalvik虚拟机实例,每次孵化新的应用进程时,都会将这个Dalvik虚拟机实例复制到新的应用程序进程里面,从而使得每个应用程序进程都有一个独立的Dalvik虚拟机实例. zygote进程的主要作用有两个: 启动SystemServer. 孵化应用

  • Android Gradle同步优化详解

    目录 动态修改gradle配置 hook agp ProjectsServices 方法签名检查是否存在support包 年初开始我们就开始了关于Gradle Sync阶段的优化.之前和大家都简单的介绍过工程相关的背景情况了,我们大概有400+的Module,然后一次的同步时间就非常的慢,我们迫切的需要对这个问题进行优化.大部分工作都是和团队内的同学一起完成的,我也只出了一点点力而已. 这次写文章真的很倒霉,之前忘了保存导致要重新开始写了.如果不是白嫖了掘金的端午礼盒,拿人手短啊,我已经打算鸽了

  • Android ActivityManagerService启动流程详解

    目录 概述 AMS的启动流程 启动流程图 概述 AMS是系统的引导服务,应用进程的启动.切换和调度.四大组件的启动和管理都需要AMS的支持.从这里可以看出AMS的功能会十分的繁多,当然它并不是一个类承担这个重责,它有一些关联类. AMS的启动流程 1:SystemServer#main -> 2:SystemServer#run -> 3:SystemServiceManager#startBootstrapServices 1:首先SystemServer进程运行main函数, main函数

  • Android实现定时器的五种方法实例详解

    一.Timer Timer是Android直接启动定时器的类,TimerTask是一个子线程,方便处理一些比较复杂耗时的功能逻辑,经常与handler结合使用. 跟handler自身实现的定时器相比,Timer可以做一些复杂的处理,例如,需要对有大量对象的list进行排序,在TimerTask中执行不会阻塞子线程,常常与handler结合使用,在处理完复杂耗时的操作后,通过handler来更新UI界面. timer.schedule(task, delay,period); task: Time

  • Android Fragment(动态,静态)碎片详解及总结

    Android Fragment(动态,静态)碎片详解 一.Fragment的相关概念(一)Fragment的基础知识 Fragment是Android3.0新增的概念,中文意思是碎片,它与Activity十分相似,用来在一个 Activity中描述一些行为或一部分用户界面.使用多个Fragment可以在一个单独的Activity中建 立多个UI面板,也可以在多个Activity中使用Fragment. Fragment拥有自己的生命 周期和接收.处理用户的事件,这样就不必在Activity写一

  • Android AsyncTask实现异步处理任务的方法详解

    Android AsyncTask实现异步处理任务的方法详解 在开发Android应用时必须遵守单线程模型的原则:Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行. Android 单线程模型概念详解:http://www.jb51.net/article/112165.htm 在单线程模型中始终要记住两条法则: 不要阻塞UI线程 确保只在UI线程中访问Android UI工具包 当一个程序第一次启动时,Android会同时启动一个对应的主线程(Main Thread),

随机推荐