文档章节

Android 使用android-support-multidex解决Dex超出方法数的限制问题

z
 zlvsdyh
发布于 2015/04/18 14:31
字数 2441
阅读 33
收藏 1
点赞 0
评论 0

如有转载,请声明出处: 时之沙: http://blog.csdn.net/t12x3456    (来自时之沙的csdn博客)

         随着应用不断迭代,业务线的扩展,应用越来越大(比如集成了各种第三方sdk或者公共支持的jar包,项目耦合性高,重复作用的类越来越多),相信很多人都遇到过如下的错误:

[java] view plaincopy在CODE上查看代码片派生到我的代码片

  1. UNEXPECTED TOP-LEVEL EXCEPTION:  

  2. java.lang.IllegalArgumentException: method ID not in [00xffff]: 65536  

  3. at com.android.dx.merge.DexMerger$6.updateIndex(DexMerger.java:501)  

  4. at com.android.dx.merge.DexMerger$IdMerger.mergeSorted(DexMerger.java:282)  

  5. at com.android.dx.merge.DexMerger.mergeMethodIds(DexMerger.java:490)  

  6. at com.android.dx.merge.DexMerger.mergeDexes(DexMerger.java:167)  

  7. at com.android.dx.merge.DexMerger.merge(DexMerger.java:188)  

  8. at com.android.dx.command.dexer.Main.mergeLibraryDexBuffers(Main.java:439)  

  9. at com.android.dx.command.dexer.Main.runMonoDex(Main.java:287)  

  10. at com.android.dx.command.dexer.Main.run(Main.java:230)  

  11. at com.android.dx.command.dexer.Main.main(Main.java:199)  

  12. at com.android.dx.command.Main.main(Main.java:103)  


       没错,你的应用中的Dex 文件方法数超过了最大值65536的上限,简单来说,应用爆棚了.

        那么让我们看一下为什么会引起这种错误:

        在Android系统中,一个App的所有代码都在一个Dex文件里面。Dex是一个类似Jar的存储了多有Java编译字节码的归档文件。因为Android系统使用Dalvik虚拟机,所以需要把使用Java Compiler编译之后的class文件转换成Dalvik能够执行的class文件。这里需要强调的是,DexJar一样是一个归档文件,里面仍然是Java代码对应的字节码文件。当Android系统启动一个应用的时候,有一步是对Dex进行优化,这个过程有一个专门的工具来处理,叫DexOptDexOpt的执行过程是在第一次加载Dex文件的时候执行的。这个过程会生成一个ODEX文件,即Optimised Dex。执行ODex的效率会比直接执行Dex文件的效率要高很多。但是在早期的Android系统中,DexOpt有一个问题,也就是这篇文章想要说明并解决的问题。DexOpt会把每一个类的方法id检索起来,存在一个链表结构里面。但是这个链表的长度是用一个short类型来保存的,导致了方法id的数目不能够超过65536个。当一个项目足够大的时候,显然这个方法数的上限是不够的。尽管在新版本的Android系统中,DexOpt修复了这个问题,但是我们仍然需要对低版本的Android系统做兼容.

        目前比较常用的方法:(1) 应用插件化,比如使用我正在参与开发的插件化框架 : https://github.com/singwhatiwanna/dynamic-load-apk ,如果有建议或者相关的问题,欢迎到Github上 积极参与. (2) 分割Dex,多工程: 把所需要的.class文件或者是Jar文件和一些源码一起编译生成一个Jar文件。然后使用Android SDK提供的dx工具把Jar文件转成Dex文件。我们可以提前对它进行ODex操作,让它在被DexClassLoader加载的时候,跳过 DexOpt的部分工作,从而加快加载的过程. 这两种方法并不冲突,插件化除了解决应用爆棚,还有很多其他的优点,可以看我之前的文章,不再复述.

        当然,Google看来也意识到了目前应用方法数爆棚的问题, 目前在已经在API 21中提供了通用的解决方案,那就是android-support-multidex.jar. 这个jar包最低可以支持到API 4的版本(Android L及以上版本会默认支持mutidex).

让我们看一下如何应用android-support-multidex.jar(以下都以在Anroid studio中的使用为例,使用eclipse开发需要安装gradle插件,其他基本上相同):


首先可以用--multi-dex配置(build.gradle)进行解决,生成的Apk中将包含多个dex文件,比如classes.dex, classes2.dex. 如下,需要对build.gradle进行修改:

[java] view plaincopy在CODE上查看代码片派生到我的代码片

  1. afterEvaluate {  

  2.     tasks.matching {  

  3.         it.name.startsWith('dex')  

  4.     }.each { dx ->  

  5.         if (dx.additionalParameters == null) {  

  6.             dx.additionalParameters = []  

  7.         }  

  8.         dx.additionalParameters += '--multi-dex' // enable multidex  

  9.   

  10.         // optional  

  11.         // dx.additionalParameters += "--main-dex-list=$projectDir/<filename>".toString() // enable the main-dex-list  

  12.     }  

  13. }  




但是默认的Dalvik 类加载器只会寻找classes.dex,所以需要将它们进行合并才能使得被识别

当然,现在有了android.support.multidex.jar的支持,一切都会变得非常简单,首先我们看一下相关源码的目录,具体的原理分析我会在之后的文章中进行讲解:

[java] view plaincopy在CODE上查看代码片派生到我的代码片

  1. android/support/multidex/BuildConfig.class  

  2. android/support/multidex/MultiDex$V14.class  

  3. android/support/multidex/MultiDex$V19.class  

  4. android/support/multidex/MultiDex$V4.class  

  5. android/support/multidex/MultiDex.class  

  6. android/support/multidex/MultiDexApplication.class  

  7. android/support/multidex/MultiDexExtractor$1.class  

  8. android/support/multidex/MultiDexExtractor.class  

  9. android/support/multidex/ZipUtil$CentralDirectory.class  

  10. android/support/multidex/ZipUtil.class  


具体集成:
将如下配置加入工程 classPath
 

[java] view plaincopy在CODE上查看代码片派生到我的代码片

  1. repositories {  

  2.   jcenter()  

  3. }  

  4.   

  5. dependencies {  

  6.   compile 'com.google.android:multidex:0.1'  

  7. }  


MultiDex实现原理:

        Apk在运行的时候,有一个dexpathlist,而Multidex的源码中,会根据你的系统版本号对dexpathlist做修改,将所有的dex都添加到dexpathlist中.



接下来集成有两个步骤:

一. 从sdk\extras\android\support\multidex\library\libs 目录将android-support-multidex.jar导入工程中

二. 如果你的工程中已经含有Application类,那么让它继承android.support.multidex.MultiDexApplication类,

     如果你的Application已经继承了其他类并且不想做改动,那么还有另外一种使用方式,覆写attachBaseContext()方法:



[java] view plaincopy在CODE上查看代码片派生到我的代码片

  1. public class MyApplication extends FooApplication {  

  2.     @Override  

  3.     protected void attachBaseContext(Context base) {  

  4.         super.attachBaseContext(base);  

  5.         MultiDex.install(this);  

  6.     }  

  7. }  



最后给出build.gradle中的完整配置:

[java] view plaincopy在CODE上查看代码片派生到我的代码片

  1. buildscript {  

  2.     repositories {  

  3.         jcenter()  

  4.     }  

  5.     dependencies {  

  6.         classpath 'com.android.tools.build:gradle:0.13.3'  

  7.         classpath 'jp.leafytree.gradle:gradle-android-scala-plugin:1.1'  

  8.     }  

  9. }  

  10.   

  11. apply plugin: 'com.android.application'  

  12. apply plugin: 'jp.leafytree.android-scala'  

  13.   

  14. repositories {  

  15.     jcenter()  

  16. }  

  17.   

  18. android {  

  19.     compileSdkVersion 21  

  20.     buildToolsVersion '21.0.2'  

  21.   

  22.     defaultConfig {  

  23.         applicationId 'some.app'  

  24.         minSdkVersion 19  

  25.         targetSdkVersion 19  

  26.         versionCode 1  

  27.         versionName '1.0'  

  28.     }  

  29. }  

  30.   

  31. dependencies {  

  32.     compile 'com.google.android:multidex:0.1'  

  33.     compile 'com.android.support:support-v4:19.0.1'  

  34.     compile 'com.google.android.gms:play-services:5.0.77'  

  35.     compile 'org.scala-lang:scala-library:2.11.2'  

  36.     compile 'org.scaloid:scaloid_2.11:3.4-10'  

  37. }  




使用MutiDex的主意事项


一. 如果你继承了MutiDexApplication或者覆写了Application中的attachBaseContext()方法.

Application类中逻辑的注意事项:

Application 中的静态全局变量会比MutiDex instal()方法优先加载,所以建议避免在Application类中使用main classes.dex文件中可能使用到的静态变量,可以根据如下所示的方式进行修改:

[java] view plaincopy在CODE上查看代码片派生到我的代码片

  1. @Override  

  2.     public void onCreate() {  

  3.         super.onCreate();  

  4.   

  5.         final Context mContext = this;  

  6.         new Runnable() {  

  7.   

  8.             @Override  

  9.             public void run() {  

  10.                 // put your logic here!  

  11.                 // use the mContext instead of this here  

  12.             }  

  13.         }.run();  

  14.     }  


二. 虽然Google解决了应用总方法数限制的问题,但并不意味着开发者可以任意扩大项目规模。Multidex仍有一些限制:

  1. DEX文件安装到设备的过程非常复杂,如果第二个DEX文件太大,可能导致应用无响应。此时应该使用ProGuard减小DEX文件的大小。

  2. 由于Dalvik linearAlloc的Bug,应用可能无法在Android 4.0之前的版本启动,如果你的应用要支持这些版本就要多执行测试。

  3. 同 样因为Dalvik linearAlloc的限制,如果请求大量内存可能导致崩溃。Dalvik linearAlloc是一个固定大小的缓冲区。在应用的安装过程中,系统会运行一个名为dexopt的程序为该应用在当前机型中运行做准备。 dexopt使用LinearAlloc来存储应用的方法信息。Android 2.2和2.3的缓冲区只有5MB,Android 4.x提高到了8MB或16MB。当方法数量过多导致超出缓冲区大小时,会造成dexopt崩溃。

  4. Multidex构建工具还不支持指定哪些类必须包含在首个DEX文件中,因此可能会导致某些类库(例如某个类库需要从原生代码访问Java代码)无法使用。

避免应用过大、方法过多仍然是Android开发者要注意的问题。Mihai Parparita的开源项目dex-method-counts可以用于统计APK中每个包的方法数量。

通常开发者自己的代码很难达到这样的方法数量限制,但随着第三方类库的加入,方法数就会迅速膨胀。因此选择合适的类库对Android开发者来说尤为重要。

开 发者应该避免使用Google Guava这样的类库,它包含了13000多个方法。尽量使用专为移动应用设计的Lite/Android版本类库,或者使用小类库替换大类库,例如用 Google-gson替换Jackson JSON。而对于Google Protocol Buffers这样的数据交换格式,其标准实现会自动生成大量的方法。采用Square Wire的实现则可以很好地解决此问题。


常见问题


DexException: Library dex files are not supported in multi-dex mode,你可能会见到如下的错误:


[java] view plaincopy在CODE上查看代码片派生到我的代码片

  1. Error:Execution failed for task ':app:dexDebug'.  

  2. > com.android.ide.common.internal.LoggedErrorException: Failed to run command:  

  3.     $ANDROID_SDK/build-tools/android-4.4W/dx --dex --num-threads=4 --multi-dex  

  4.     ...  

  5.   Error Code:  

  6.     2  

  7.   Output:  

  8.     UNEXPECTED TOP-LEVEL EXCEPTION:  

  9.     com.android.dex.DexException: Library dex files are not supported in multi-dex mode  

  10.         at com.android.dx.command.dexer.Main.runMultiDex(Main.java:322)  

  11.         at com.android.dx.command.dexer.Main.run(Main.java:228)  

  12.         at com.android.dx.command.dexer.Main.main(Main.java:199)  

  13.         at com.android.dx.command.Main.main(Main.java:103)  



 对于dex 的--multi-dex 选项设置与预编译的library工程有冲突,因此如果你的应用中包含引用的lirary工程,需要将预编译设置为false:

[java] view plaincopy在CODE上查看代码片派生到我的代码片

  1. android {  

  2.     // ...  

  3.     dexOptions {  

  4.         preDexLibraries = false  

  5.     }  

  6. }  




OutOfMemoryError: Java heap space

当运行时如果看到如下错误:

[java] view plaincopy在CODE上查看代码片派生到我的代码片

  1. UNEXPECTED TOP-LEVEL ERROR:  

  2. java.lang.OutOfMemoryError: Java heap space  


dexOptions中有一个字段用来增加java堆内存大小:

[java] view plaincopy在CODE上查看代码片派生到我的代码片

  1. android {  

  2.     // ...  

  3.     dexOptions {  

  4.         javaMaxHeapSize "2g"  

  5.     }  

  6. }      

  7.           


使用eclipse的开发人员可以参考Google官方文档

https://developer.android.com/tools/building/multidex.html#mdex-gradle

需要安装支持gradle构建的插件,下载地址:

http://dist.springsource.com/release/TOOLS/gradle  (目前可能需要翻墙)



参考相关资料:

1. MutiDex 官方文档: https://developer.android.com/reference/android/support/multidex/MultiDex.html

2. http://blog.osom.info/2014/10/multi-dex-to-rescue-from-infamous-65536.html

另附android -support-mutidex.jar下载地址: http://download.csdn.net/detail/t12x3456/8143383


补充注意事项:

[java] view plaincopy在CODE上查看代码片派生到我的代码片

  1. com.android.dex.DexException: Multiple dex files define L{package}/BuildConfig;  

如果遇到这个错误请进行如下检查:

1. 主工程与依赖library工程包名是否重复
2. 检查主工程与依赖library工程是否含有重复的support.jar或者其他jar包


解决方案:

1. 修改library工程包名

2. 删除重复jar包

3.手工添加lib包,增加如下配置

[java] view plaincopy在CODE上查看代码片派生到我的代码片

  1. dependencies {  

  2.             compile fileTree(dir: 'libs', include: ['*.jar'])  

  3.             compile project(':lib-project-module')  

  4.         } 


本文转载自:http://blog.csdn.net/t12x3456

共有 人打赏支持
z
粉丝 0
博文 4
码字总数 516
作品 0
深圳
Android关于Dex拆分(MultiDex)技术详解

一、前言 关于Android中的分包技术,已经不是什么新的技术了,网上也有很多解析了,但是他们都是给了理论上的知道和原理解析,并没有详细的案例说明,所以这里我们就来详细讲解一下Android中...

tan6458 ⋅ 2017/01/10 ⋅ 0

Android高级之十三讲-HotFix、热加载和热更新

本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 组件化与插件化:前者对功能进行拆分后,独立开发,打成一个包发布;后者对功能拆分,使用主包+分包,可以分别独立发布。...

liuzxgeek ⋅ 2016/12/14 ⋅ 0

彻底掌握Android多分包技术MultiDex-用Ant和Gradle分别构建(一)

Andrid多分包技术在大型项目编译方面起着至关重要的作用,作为一个高级开发者我们有必要掌握此技能,现在我带领大家统一学习此项技能,并教会大家分别使用Ant和Gradle构建。 什么是Dex Dex是...

tan6458 ⋅ 2017/01/09 ⋅ 0

彻底掌握Android多分包技术MultiDex-用Ant和Gradle分别构建(二)

在上一篇博客中,我给大家详细介绍了ant实现Android多分包技术,具体可以点击查看:彻底掌握Android多分包技术MultiDex-用Ant和Gradle分别构建(一);接下来的这篇博客我将带领大家一起学习...

tan6458 ⋅ 2017/01/09 ⋅ 0

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

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

DJY1992 ⋅ 2016/04/15 ⋅ 0

微信Android客户端架构演进之路

去年7月,笔者在InfoQ举办的ArchSummit深圳2014的架构师峰会上,分享了微信Android客户端的架构演进史。可以说,这是一个典型的Android应用在从小到大的成长过程中的“踩坑”与“填坑”的历史...

浮躁的码农 ⋅ 05/29 ⋅ 0

Android应用打破65K方法数限制

近日,Android Developers在Google+上宣布了新的Multidex支持库,为方法总数超过65K的Android应用提供了官方支持。 如果你是一名幸运的Android应用开发者,正在开发一个前景广阔的应用,不断...

____追梦人 ⋅ 2016/02/23 ⋅ 0

Android P 应用兼容常见错误及建议

从 2018 年 3 月初我们发布 Android P 开发者预览版以来,很多开发者都对当前常见应用在 Android P 上做了一些兼容性测试,我们在这里总结了一些常见的问题,以及它们发生的原因和建议的修改...

谷歌开发者 ⋅ 04/25 ⋅ 0

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

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

DJY1992 ⋅ 2016/04/15 ⋅ 0

非 SDK 接口常见问题 | Android 开发者 FAQ Vol.13

常规问题 Q1: 什么是非 SDK 接口? A:非 SDK 接口指不在官方Android SDK涵盖范围内的 Java 字段和方法。此类接口是 SDK 的内部实现细节,可能随时会被修改,且不对开发者另行通知。 常规问题...

谷歌开发者 ⋅ 06/19 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

使用 vue-cli 搭建项目

vue-cli 是一个官方发布 vue.js 项目脚手架,使用 vue-cli 可以快速创建 vue 项目,GitHub地址是:https://github.com/vuejs/vue-cli 一、 安装 node.js 首先需要安装node环境,可以直接到中...

初学者的优化 ⋅ 13分钟前 ⋅ 0

设计模式 之 享元模式

设计模式 之 享元模式 定义 使用共享技术来有效地支持大量细粒度对象的复用 关键点:防止类多次创建,造成内存溢出; 使用享元模式来将内部状态与外部状态进行分离,在循环创建对象的环境下,...

GMarshal ⋅ 28分钟前 ⋅ 0

SpringBoot集成Druid的最简单的小示例

参考网页 https://blog.csdn.net/king_is_everyone/article/details/53098350 建立maven工程 Pom文件 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM......

karma123 ⋅ 今天 ⋅ 0

Java虚拟机基本结构的简单记忆

Java堆:一般是放置实例化的对象的地方,堆分新生代和老年代空间,不断未被回收的对象越老,被放入老年代空间。分配最大堆空间:-Xmx 分配初始堆空间:-Xms,分配新生代空间:-Xmn,新生代的大小一...

算法之名 ⋅ 今天 ⋅ 0

OSChina 周日乱弹 —— 这么好的姑娘都不要了啊

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @TigaPile :分享曾惜的单曲《讲真的》 《讲真的》- 曾惜 手机党少年们想听歌,请使劲儿戳(这里) @首席搬砖工程师 :怎样约女孩子出来吃饭,...

小小编辑 ⋅ 今天 ⋅ 8

Jenkins实践3 之脚本

#!/bin/sh# export PROJ_PATH=项目路径# export TOMCAT_PATH=tomcat路径killTomcat(){pid=`ps -ef | grep tomcat | grep java|awk '{print $2}'`echo "tom...

晨猫 ⋅ 今天 ⋅ 0

Spring Bean的生命周期

前言 Spring Bean 的生命周期在整个 Spring 中占有很重要的位置,掌握这些可以加深对 Spring 的理解。 首先看下生命周期图: 再谈生命周期之前有一点需要先明确: Spring 只帮我们管理单例模...

素雷 ⋅ 今天 ⋅ 0

zblog2.3版本的asp系统是否可以超越卢松松博客的流量[图]

最近访问zblog官网,发现zlbog-asp2.3版本已经进入测试阶段了,虽然正式版还没有发布,想必也不久了。那么作为aps纵横江湖十多年的今天,blog2.2版本应该已经成熟了,为什么还要发布这个2.3...

原创小博客 ⋅ 今天 ⋅ 0

聊聊spring cloud的HystrixCircuitBreakerConfiguration

序 本文主要研究一下spring cloud的HystrixCircuitBreakerConfiguration HystrixCircuitBreakerConfiguration spring-cloud-netflix-core-2.0.0.RELEASE-sources.jar!/org/springframework/......

go4it ⋅ 今天 ⋅ 0

二分查找

二分查找,也称折半查找、二分搜索,是一种在有序数组中查找某一特定元素的搜索算法。搜素过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜素过程结束;如果某一特定元素大于...

人觉非常君 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部