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 下,添加:
compile 'com.android.support:multidex:1.0.1'
在application中:(没有继承其他application的,使用MultidexApplication,如果继承了其他application的,就用如下方式加载)
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
//因为引用的包过多,实现多包问题
MultiDex.install(this);
}
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
的文件,如下所示:
com/example/MyClass.class
com/example/MyOtherClass.class
然后,您可以按以下方式针对构建类型声明该文件:
android {
buildTypes {
release {
multiDexKeepFile file 'multidex-config.txt'
...
}
}
}
请记住,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 MultiDex的更多相关文章
- Android MultiDex兼容包怎么使用?
在Android系统中安装应用的时候,需要对Dex进行优化,但由于其处理工具DexOpt的限制,导致其id的数目不能够超过65536个.而MultiDex兼容包的出现,就很好的解决了这个问题,它可以配 ...
- android MultiDex multidex原理下超出方法数的限制问题(三)
android MultiDex 原理下超出方法数的限制问题(三) 插件化?自动化?multiDex?是不是觉得已经懵逼了?请先看这篇文章的内容,在下篇文章中将会详解具体的过程- 随着应用不断迭 ...
- android MultiDex multidex原理原理下遇见的N个深坑(二)
android MultiDex 原理下遇见的N个深坑(二) 这是在一个论坛看到的问题,其实你不知道MultiDex到底有多坑. 不了解的可以先看上篇文章:android MultiDex multi ...
- android MultiDex multiDex原理(一)
android MultiDex 原理(一) Android分包MultiDex原理详解 转载请注明:http://blog.csdn.net/djy1992/article/details/5116 ...
- Android 65535 问题与 MultiDex分包
Android Multidex 遇到的问题 http://blog.csdn.net/wangbaochu/article/details/51178881 Android 使用android-su ...
- Android 分Dex (MultiDex)
需要分Dex的理由想必大家都知道了.正是在ART以前的Android系统中,Dex文件对于方法索引是用一个short类型的数据来存放的.而short的最大值是65535,因此当项目足够大包含方法数目足 ...
- Android分包MultiDex原理详解
MultiDex的产生背景 当Android系统安装一个应用的时候,有一步是对Dex进行优化,这个过程有一个专门的工具来处理,叫DexOpt.DexOpt的执行过程是在第一次加载Dex文件的时候执行的 ...
- 在android开发中使用multdex的方法-IT蓝豹为你整理
Android系统在安装应用时,往往需要优化Dex,而由于处理工具DexOpt对id数目的限制,导致其处理的数目不能超过65536个,因此在Android开发中,需要使用到MultiDex来解决这个问 ...
- 【Android端 APP 启动时长获取】启动时长获取方案及具体实施
一.什么是启动时长? 1.启动时长一般包括三种场景,分别是:新装包的首次启动时长,冷启动时长.热启动时长 冷启动 和 热启动 : (1)冷启动:当启动应用时,后台没有该程序的进程,此时启动的话系统会分 ...
随机推荐
- AJAX XML返回类型
例题 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.o ...
- Ext.MessageBox消息框
Ext JS消息提示框主要包括:alert.confirm.prompt.show 1.Ext.MessageBox.alert() 调用格式: alert( String title, String ...
- [LeetCode] Happy Number
Happy Number Total Accepted: 35195 Total Submissions: 106936 Difficulty: Easy Write an algorithm to ...
- 命令行登陆Oracle(包括远程登陆)
本方法适用于在cmd命令行窗口以及pl/sql登陆Oracle下登录本机或者远程Oracle. 1.首先保证在当前主机上设置了ORACLE_HOME环境变量: 例如:ORACLE_HOME=D ...
- js或jquery实现页面打印可局部打印
方法一:直接用js的打印方法 <input id="btnPrint" type="button" value="打印" onclic ...
- 轻松学习RSA加密算法原理
转自:http://blog.csdn.net/sunmenggmail/article/details/11994013 http://blog.csdn.net/q376420785/articl ...
- ZLL主机接口的信息处理流程
主机接口的信息处理流程 在我们翻译的文档中是用电脑端来模拟主机的,电脑代替网关发送主机接口命令的环节是在zll_controller.c中实现的,(在下载的文件中已经提供了其对应的可执行文件zllCm ...
- [Unity3D]上海某大型游戏公司的基础面试题
一个小老乡跟我聊到去上海某大公司的基础面试题,面试结果不尽如人意,但还是分享了下面试的试题,刚刚第一次录制视频,给某人讲课,我感觉讲的还算比较耐心,但发现一些新手入门学习的弊端,可能是很普遍的现象,这 ...
- barabasilab-networkScience学习笔记3-随机网络模型
第一次接触复杂性科学是在一本叫think complexity的书上,Allen博士很好的讲述了数据结构与复杂性科学,barabasi是一个知名的复杂性网络科学家,barabasilab则是他所主导的 ...
- javascript的笔记精简版
在写javascript的代码时一定要用单引号或者双引号括起来,不带引号的话就以字符串来处理 在javascript里面不能以纯数字或者click命名函数或者变量 要想修改标签的属性,在html里面怎 ...