出现的原因:

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关于Dex拆分(MultiDex)技术的解析

Android MultiDex的更多相关文章

  1. Android MultiDex兼容包怎么使用?

    在Android系统中安装应用的时候,需要对Dex进行优化,但由于其处理工具DexOpt的限制,导致其id的数目不能够超过65536个.而MultiDex兼容包的出现,就很好的解决了这个问题,它可以配 ...

  2. android MultiDex multidex原理下超出方法数的限制问题(三)

    android MultiDex 原理下超出方法数的限制问题(三)    插件化?自动化?multiDex?是不是觉得已经懵逼了?请先看这篇文章的内容,在下篇文章中将会详解具体的过程- 随着应用不断迭 ...

  3. android MultiDex multidex原理原理下遇见的N个深坑(二)

    android MultiDex 原理下遇见的N个深坑(二) 这是在一个论坛看到的问题,其实你不知道MultiDex到底有多坑. 不了解的可以先看上篇文章:android MultiDex multi ...

  4. android MultiDex multiDex原理(一)

    android MultiDex 原理(一) Android分包MultiDex原理详解 转载请注明:http://blog.csdn.net/djy1992/article/details/5116 ...

  5. Android 65535 问题与 MultiDex分包

    Android Multidex 遇到的问题 http://blog.csdn.net/wangbaochu/article/details/51178881 Android 使用android-su ...

  6. Android 分Dex (MultiDex)

    需要分Dex的理由想必大家都知道了.正是在ART以前的Android系统中,Dex文件对于方法索引是用一个short类型的数据来存放的.而short的最大值是65535,因此当项目足够大包含方法数目足 ...

  7. Android分包MultiDex原理详解

    MultiDex的产生背景 当Android系统安装一个应用的时候,有一步是对Dex进行优化,这个过程有一个专门的工具来处理,叫DexOpt.DexOpt的执行过程是在第一次加载Dex文件的时候执行的 ...

  8. 在android开发中使用multdex的方法-IT蓝豹为你整理

    Android系统在安装应用时,往往需要优化Dex,而由于处理工具DexOpt对id数目的限制,导致其处理的数目不能超过65536个,因此在Android开发中,需要使用到MultiDex来解决这个问 ...

  9. 【Android端 APP 启动时长获取】启动时长获取方案及具体实施

    一.什么是启动时长? 1.启动时长一般包括三种场景,分别是:新装包的首次启动时长,冷启动时长.热启动时长 冷启动 和 热启动 : (1)冷启动:当启动应用时,后台没有该程序的进程,此时启动的话系统会分 ...

随机推荐

  1. jQueryEasyUi验证

        多重验证: { field : 'startPort', title : "起始端口", editor: "text", width : 50, edi ...

  2. 菜鸟学Linux命令:lsof命令 查找指定用户、进程、端口打开的文件

    lsof,list open files, 是一个列出当前系统打开文件的工具.在linux环境下,任何事物都以文件的形式存在,通过文件不仅仅可以访问常规数据,还可以访问网络连接和硬件. 命令格式:ls ...

  3. 拷贝,集合,函数,enumerate,内置函数

    1.拷贝 字符串和数字.赋值 id一样 import copy #提供拷贝功能 copy.copy() #原来的和现在的一起修改,不用修改时用浅copy,节省内存,复制最外层 copy.deepcop ...

  4. 豆瓣的账号登录及api操作

    .douban.php <?php /** * PHP Library for douban.com * * @author */ class doubanPHP { function __co ...

  5. UDP穿透NAT原理解析

    转自:http://www.2cto.com/net/201201/116793.html NAT(Network Address Translators),网络地址转换:网络地址转换是在IP地址日益 ...

  6. Eclipse·如何关联Git库文件和添加JUint库

    Eclipse创建工程并关联到文件(SVN或Git管理的代码文件) 新建java工程,用于存放工程的一些信息,默认存放地址. 工程相关的信息是不需要提交到(SVN或Git)版本库的,所以工程存放到本地 ...

  7. 用户层获取TEB PEB结构地址 遍历进程模块.doc

    1.fs寄存器指向TEB结构 2.在TEB+0x30地方指向PEB结构 3.在PEB+0x0C地方指向PEB_LDR_DATA结构 4.在PEB_LDR_DATA+0x1C地方就是一些动态连接库地址了 ...

  8. codeforces733D. Kostya the Sculptor 偏序cmp排序,数据结构hash,代码简化

    对于n==100.1,1,2或者1,2,2大量重复的形状相同的数据,cmp函数最后一项如果表达式带等于,整个程序就会崩溃 还没有仔细分析std::sort的调用过程,所以这里不是很懂..,mark以后 ...

  9. Servlet域对象ServletContext小应用------计算网站访问量

    package cn.yzu; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.Servlet ...

  10. 【转】CDH5.x升级

    http://www.cloudera.com/content/www/zh-CN/documentation/enterprise/5-3-x/topics/cm_ag_upgrade_cm5.ht ...