Android MultiDex

出现的原因:

Android 5.0 之前版本的 Dalvik 可执行文件分包支持

Android 5.0(API 级别 21)之前的平台版本使用 Dalvik 运行时来执行应用代码。默认情况下,Dalvik 限制应用的每个 APK 只能使用单个 classes.dex 字节码文件。要想绕过这一限制,您可以使用 MultiDex,它会成为您的应用主要 DEX 文件的一部分,然后管理对其他 DEX 文件及其所包含代码的访问。

当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的功能。

Android 5.0 及更高版本的 Dalvik 可执行文件分包支持

Android 5.0(API 级别 21)及更高版本使用名为 ART 的运行时,后者原生支持从 APK 文件加载多个 DEX 文件。ART 在应用安装时执行预编译,扫描 classesN.dex 文件,并将它们编译成单个 .oat 文件,供 Android 设备执行。因此,如果您的 minSdkVersion 为 21 或更高值,则不需要 Dalvik 可执行文件分包支持库。

如需了解有关 Android 5.0 运行时的详细信息,请参阅 ART 和 Dalvik

目前在已经在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

.Java文件编译生成字节码文件,然后打包生成.dex时会按照类,方法等进行分类,使用IndexMap结构处理。

所有的方法都存放在short[] 里, 而short的大小正是65K。 

method的数量远多于class的数量,method的数量会先到65K,class、field同样存在65k的大小限制。

统计时是把系统的,第三方的加上自己的,方法数量的合,都放到这个数组里进行计算, frameworkMethodids + libraryMdthodids + mycodemethodids。

2、在2.3系统之前,虚拟机内存只分配了5M

使用:

multidex是一个文档齐全的成熟的解决方案。强烈推荐遵循安卓开发者网站上的指示来启用multidex。也可以参考github上的项目样例

在app/build.gradle 下,添加:

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     }

Multidex的方式的局限性:

(1)如果DEX文件太大,安装分割dex文件是一个复杂的过程,可能会导致应用程序无响应(ANR)的错误。在这种情况下,你应该尽量的减小dex文件的大小和删除无用的逻辑,而不是完全依赖于multidex。

(2)在Android 4.0设备(API Level 14)之前,由于Dalvik linearalloc bug(问题22586),multidex很可能是无法运行的。如果希望运行在Level 14之前的Android系统版本,请先确保完整的测试和使用。

(3)应用程序使用了multiedex,需要申请很多的一块内存,会造成使用比较大的内存,当然,可能还会引起dalvik虚拟机的崩溃(issue 78035)。

(4)对于应用程序比较复杂的,存在较多的library的项目。multidex可能会造成不同依赖项目间的dex文件函数相互调用,找不到方法。

声明主 DEX 文件中需要的类

为 Dalvik 可执行文件分包构建每个 DEX 文件时,构建工具会执行复杂的决策制定来确定主要 DEX 文件中需要的类,以便应用能够成功启动。如果启动期间需要的任何类未在主 DEX 文件中提供,那么您的应用将崩溃并出现错误 java.lang.NoClassDefFoundError

该情况不应出现在直接从应用代码访问的代码上,因为构建工具能识别这些代码路径,但可能在代码路径可见性较低(如使用的库具有复杂的依赖项)时出现。例如,如果代码使用自检机制或从原生代码调用 Java 方法,那么这些类可能不会被识别为主 DEX 文件中的必需项。

因此,如果您收到 java.lang.NoClassDefFoundError,则必须使用构建类型中的 multiDexKeepFile 或 multiDexKeepProguard 属性声明它们,以手动将这些其他类指定为主 DEX 文件中的必需项。如果类在 multiDexKeepFile 或 multiDexKeepProguard 文件中匹配,则该类会添加至主 DEX 文件。

multiDexKeepFile 属性

您在 multiDexKeepFile 中指定的文件应该每行包含一个类,并且采用 com/example/MyClass.class 的格式。例如,您可以创建一个名为 multidex-config.txt 的文件,如下所示:

1 com/example/MyClass.class
2 com/example/MyOtherClass.class

然后,您可以按以下方式针对构建类型声明该文件:

1 android {
2     buildTypes {
3         release {
4             multiDexKeepFile file 'multidex-config.txt'
5             ...
6         }
7     }
8 }

请记住,Gradle 会读取相对于 build.gradle 文件的路径,因此如果 multidex-config.txt 与 build.gradle 文件在同一目录中,以上示例将有效。

解决ANR的方法: 

总体思路是:把这个MultiDex.install(this)放到异步线程中加载,同时又要保证在用户使用功能之前,异步线程已经把非主dex加载完成。

从编译到运行起来,发生什么了呢?

1. 编译生成 allclasses.jar 生成classes.dex, classes2.dex, .... 多个。

安装阶段:把.dex 优化成classes.odex, 注意,只优化第一个dex文件。

首次运行:

执行MultiDex.install()必然会再次对classes2.dex执行dexopt等操作,所有这些操作必须在5秒内完成,否则就ANR。

非首次启动则直接从cache中读取已经执行过dexopt的ODEX文件,这个过程对启动并无太大影响

1. dexpath BaseClassLoader.pathlist 类, 把所有的dex都加载。解决了为什么多个dex里的方法也能被正确的index到,不会导致no such method 的crash

2. odex, 这个过程是非常耗时的,如果第二个dex是5-7M的话大约要加载4s, google这个默认方式时间太长,会导致ANR。

这就需要完成2个事情:

1. 把启动需要加载的类放到主dex中。

2. 显示loading界面,保证用户使用之前把dex加载完成。

主dex中的类: 

把这个MultiDex.install(this)放到异步线程中加载,主dex中存放application启动的1直接引用类,所有可能的2入口类,加上3第三方的调用。扫面这些类的直接引用类。统统放到主dex中。

首先在编译打包阶段,在build task中添加一层task, 添加Blacklist.xml自定义的黑名单, 这个名单中的方法对应的类需要添加到主dex中,保证主dex能够加载APP启动时需要的必要的类。

balcklist中包含了需要放到主dex中的类的path,addtask的这个task,把blacklist中的类放到了dextask中生成想要的dex。

如何确定哪些类需要放到blcaklist中:

粒度到方法层面,从主activity加载开始,加载了class B的public x 和public y, 则把B的x 和 y方法中,仅x, y方法中import到的类进行分析, 进行回溯添加。

那如何能让工作更高效,能让查找更有保证呢? 请看下篇,基于字节码文件分析.class文件。 

异步加载界面的现实时机:

splash界面,主界面,在收到事件之前进行监听显示load界面。直到异步加载完成所有dex。 

其他问题:

主dex不够65k方法,主dex会有多少方法? 生成dex时会一直塞直到塞满65K。

JNI层的回调类,和用到反射的类是需要放到主dex中的,这些BI,库加载都是需要运行就work的。 

参考:

第三方库 TurboDex  https://github.com/asLody/TurboDex

Android MultiDex 实现原理解析    (配置和简要原理)

Android Dex 分包指南 (Gradle build task)

Android的multidex带来的性能问题-减慢app启动速度  (解决multidex app启动性能问题)

Android关于Dex拆分(MultiDex)技术的解析

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值