杭州Android培训
达内杭州Android培训中心

13732203138

热门课程

怎样解决Android应用方法数过多

  • 时间:2018-03-27 17:46
  • 发布:杭州android中心
  • 来源:疑难解答

这是一个什么问题?

由于 Android 早期系统的一个问题,在我们开发应用中,每一个 dex 文件的方法数不能超过 65535,如果超过了这个数,APK 是没办法正常安装加载的,具体造成这个问题的原因是这样的:

当 Android 早期系统启动 APP 时,为了提升执行效率,要对 dex 文件进行优化,即 DexOpt。DexOpt 过程是在第一次加载 dex 文件的时候执行的。在早期 Android 系统中,DexOpt 会把每一个类的方法 ID 检索起来,存在一个链表结构里面。但是这个链表的长度是用一个 short 类型来保存的,导致了方法 ID 的数目不能够超过 65535 个。APP 体积足够大的时候,那么这个方法数的上限是不够的。尽管在新版的 Android 系统中,DexOpt 修复了这个问题,但是我们仍然需要对老系统做兼容。

对于造成该问题的另外一个解释:Dalvik Bytecode 的限制,因为 Dalvik 的 invoke-kind 指令集中,method reference index 只留了 16 bits,最多能引用 65535 个方法。具体参考链接:

  • http://stackoverflow.com/questions/21490382/does-the-android-art-runtime-have-the-same-method-limit-limitations-as-dalvik/21492160#21492160,

  • http://source.android.com/devices/tech/dalvik/dalvik-bytecode.html

Android 不同版本的差异

实际上 Android 这个问题在不同系统版本上又会有差异,在解决该问题时也需要兼顾考虑。

4.0 以下

不但要考虑方法数的问题,还需要考虑 LinearAlloc 的限制。主要是因为 DexOpt 使用 LinearAlloc 来存储应用的方法信息,Dalvik LinearAlloc 是一个固定大小的缓冲区,Android 2.2 和 2.3 的缓冲区只有 5MB,Android 4.0 之后进行了提升。所以 4.0 以下系统,在方法数过多超出缓冲区大小时,即使没有超出65535,也会造成 DexOpt 崩溃。即使正常编译打包成 APK,在安装的时候,也有可能会提示 INSTALL_FAILED_DEXOPT 而导致安装失败。

4.0 和 5.0

Android 使用 Dalvik 虚拟机,APP 安装时候,守护进程 installd 调用 DexOpt,它对打包在 APK 里面包含有 dex 文件字节码的 classes.dex 进行优化,优化得到的文件保存在 /data/dalvik-cache 目录中,以 .odex 为后缀名,供运行时使用。但对于 5.0 以前系统本身来说它只处理一个 classes.dex。对于多个 dex 文件情况下,是我们需要考虑解决的问题。

5.0 以上

Android 系统使用 ART 虚拟机代替了之前的 Dalvik 虚拟机。ART 模式下,APK在安装时候,守护进程 installd 调用另外一个工具 dex2oat,它对打包在 APK 里面包含有 dex 文件字节码进翻译 ,如果这时候发现多个 classes(..N).dex 文件,就会将他们最终合成为一个 .oat 的文件,并且也是保存在 /data/dalvik-cache 目录中,供 APP 运行时使用,有效的解决了的这个问题。

可以选择的解决方案

Google 原始方案

为解决 65535 问题,Google 提供了 Multidex Support 包,集成 Multidex 包之后,通过以下的方式来加载多个 dex 文件。

@Override

protected void attachBaseContext(Context base) {

    super.attachBaseContext(context);

    Multidex.install(this);

}

实际是上这种方案并没有彻底解决问题,主要的问题是:

  • 加载 dex 文件时间长

  • 由于在主线程加载 dex2 文件,造成 ANR,Crash

子进程异步加载子 dex 文件

这个方案实际上是对 Google 提供的 Multidex 方案做一些改进。

Multidex.install() 方法主要做了三件事情:

  1. 从 APK 中加压出 dex 文件

  2. 完成 DexOpt 操作

  3. 把优化后的 odex 文件目录添加到 ClassLoader 中

其中主要耗时的工作是前两项,所以基于这样的情况做下面的改进:

  1. APP 启动时主进程挂起等待

  2. 在子进程中进行 Multidex.install(this) 的工作,它从 APK 中解压 dex2 文件,然后完成 DexOpt 的工作

  3. 同时在子进程中启动欢迎界面,子进程任务完成后退出

  4. 主进程监测到子进程的解压和 DexOpt 工作完成后,再从挂起状态恢复,继续工作

  5. 此时在主进程中再调用一次 Multidex.install(this)  作用是将优化后的优化后的 odex 文件目录添加到 ClassLoader 中(由于解压出 dex2 文件,dexOpt工作在子进程中已经完成,Multidex会跳过这两项工作)

  6. 主进程启动正常的工作流程

优点:

  1. Multidex 的处理和业务逻辑耦合性很低

  2. 主 dex 文件中必须的 class 文件比较少,便于后期维护,避免手动拆分 dex 文件带来的各种问题

  3. 通用性比较强,便于机型试配,测试等

缺点:

  1. 子 dex 文件处理需要在子进程中,此时需要等待时间。可以通过控制welcome,splash页面来解决

  2. 子 dex 文件大时候,启动时间会比较长

插件化+动态加载

社区有一些比较成熟的方案,比如携程的方案,阿里的 Atlas 方案,早期 360Droid Plugin 等,思路都是大同小异:

  1. 对某些子模块进行动态加载,结合插件化的实现

  2. 重写 AAPT 对资源进程处理

  3. 对系统 API 进行 Hook,以便于启动相应的子模块的 Activity 等

  4. 通过代理 Activity Delegate 等方式解决系统组件需要注册的问题

优点: 

  1. 动态加载不会对产品体验造成影响,甚至是用户无感知的加载

  2. 基于插件化,组件化的基础上来实现更好

缺点:

  1. 机型覆盖,适配等问题。需要全方位的测试

  2. 对于Android的各个版本需要追踪,维护成本比较高

延迟加载

Instagram 有一个开源项目 lazy-module-loader,参考他们的方案我们也可以自己分 dex 文件,然后通过 ClassLoader 来加载,原理是这样:

  1. 对一些独立的 jar 或 dex 文件加载进程延迟加载

  2. 通过代理的方式来解决 Activity, Service 的启动问题

  3. 也可以做到用户无感知 

优点:

  1. 没有对系统 Framework 层进行 Hook,所以通用性比较强

  2. 后期维护成本比较低

缺点:

  1. 不支持资源的加载

  2. 手动拆分 dex 文件,成本也会比较高

雪球的选择 

根据上边的一些方案分析,我们认为雪球 APP 最适合的方案是异步加载子 dex 文件来解决这个问题,主要考虑了以下几个因素:

  1. 方法数考虑:雪球 APP 目前来说算一个中等量级的应用,方法数大概在65535-100000 之前这样的级别

  2. 版本支持:雪球目前最低支持的 Android 版本是 4.0,所以不需要考虑 4.0 以下版本的 LinearAlloc 的限制。在 5.0 以上版本由于我们方法数不是太大,使用 Android 系统的自动的 Multidex 处理方式,也不会造成太大卡顿,所以我们主要考虑处理 4.0-5.0 之间的用户

  3. 用户占比:雪球 APP 4.0-5.0 版本之间的用户的占比大约在 5% 左右,而且占比一直下降中。所以对于主业务逻辑不需要做大规模的改动,又能兼顾这5% 左右的用户,还是一个比较适合的方案

上一篇:Android技术总监是怎么样的
下一篇:Android分享功能
选择城市和中心
贵州省

广西省

海南省