相关文章推荐
强悍的斑马  ·  Android判断版本号 - J依情 ·  2 月前    · 
踢足球的火龙果  ·  微信小程序可以直接获取手机的安卓版本吗?io ...·  2 月前    · 
果断的水龙头  ·  解封时如何(以及在何处)修复android. ...·  1 月前    · 
仗义的冲锋衣  ·  安卓SDK升级26ICON适配指导_aapt ...·  1 月前    · 
豪爽的烈马  ·  在android中找不到android.su ...·  1 月前    · 
苦闷的猕猴桃  ·  燕理传媒李少华编剧作品《海边升起一座悬崖》获 ...·  1 年前    · 
重感情的水桶  ·  女皇后宫有点乱 - 🌈️包子漫畫·  1 年前    · 
风流的日记本  ·  电梯操作证怎么考? - 知乎·  1 年前    · 
开朗的咖啡  ·  商务部行政处罚决定书(商法函[2018]12号)·  1 年前    · 
考研的猕猴桃  ·  Windows server ...·  1 年前    · 
Code  ›  Android 使用android-support-multidex解决Dex超出方法数的限制问题开发者社区
android框架 android
https://cloud.tencent.com/developer/article/1031639
谦虚好学的火柴
1 年前
作者头像
xiangzhihong
0 篇文章

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

前往专栏
腾讯云
开发者社区
文档 意见反馈 控制台
首页
学习
活动
专区
工具
TVP
文章/答案/技术大牛
发布
首页
学习
活动
专区
工具
TVP
返回腾讯云官网
社区首页 > 专栏 > 向治洪 > Android 使用android-support-multidex解决Dex超出方法数的限制问题

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

作者头像
xiangzhihong
发布 于 2018-01-30 12:09:25
1.1K 0
发布 于 2018-01-30 12:09:25
举报

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

 UNEXPECTED TOP-LEVEL EXCEPTION:  
 java.lang.IllegalArgumentException: method ID not in [0, 0xffff]: 65536 
 at com.android.dx.merge.DexMerger$6.updateIndex(DexMerger.java:501)  
 at com.android.dx.merge.DexMerger$IdMerger.mergeSorted(DexMerger.java:282)  
 at com.android.dx.merge.DexMerger.mergeMethodIds(DexMerger.java:490)  
 at com.android.dx.merge.DexMerger.mergeDexes(DexMerger.java:167)  
 at com.android.dx.merge.DexMerger.merge(DexMerger.java:188)  
 at com.android.dx.command.dexer.Main.mergeLibraryDexBuffers(Main.java:439)  
 at com.android.dx.command.dexer.Main.runMonoDex(Main.java:287)  
 at com.android.dx.command.dexer.Main.run(Main.java:230)  
 at com.android.dx.command.dexer.Main.main(Main.java:199)  
 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 文件。这里需要强调的是, Dex 和 Jar 一样是一个归档文件,里面仍然是 Java 代码对应的字节码文件。当 Android 系统启动一个应用的时候,有一步是对 Dex 进行优化,这个过程有一个专门的工具来处理,叫 DexOpt 。 DexOpt 的执行过程是在第一次加载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进行修改:

 afterEvaluate {  
     tasks.matching {  
         it.name.startsWith('dex')  
     }.each { dx ->  
  if (dx.additionalParameters == null) {  
             dx.additionalParameters = []  
         dx.additionalParameters += '--multi-dex' // enable multidex 
  // optional 
  // dx.additionalParameters += "--main-dex-list=$projectDir/<filename>".toString() // enable the main-dex-list 
 }  

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

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

 android/support/multidex/BuildConfig.class 
 android/support/multidex/MultiDex$V14.class 
 android/support/multidex/MultiDex$V19.class 
 android/support/multidex/MultiDex$V4.class 
 android/support/multidex/MultiDex.class 
 android/support/multidex/MultiDexApplication.class 
 android/support/multidex/MultiDexExtractor$1.class 
 android/support/multidex/MultiDexExtractor.class 
 android/support/multidex/ZipUtil$CentralDirectory.class 
 android/support/multidex/ZipUtil.class 

具体集成:

将如下配置加入工程 classPath中

 repositories {  
   jcenter()  
 dependencies {  
   compile 'com.google.android:multidex:0.1' 
 }  

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()方法:

 public class MyApplication extends FooApplication {  
  @Override 
  protected void attachBaseContext(Context base) {  
  super.attachBaseContext(base);  
         MultiDex.install(this);  
 }  

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

 buildscript {  
     repositories {  
         jcenter()  
     dependencies {  
         classpath 'com.android.tools.build:gradle:0.13.3' 
         classpath 'jp.leafytree.gradle:gradle-android-scala-plugin:1.1' 
 apply plugin: 'com.android.application' 
 apply plugin: 'jp.leafytree.android-scala' 
 repositories {  
     jcenter()  
 android {  
     compileSdkVersion 21 
     buildToolsVersion '21.0.2' 
     defaultConfig {  
         applicationId 'some.app' 
         minSdkVersion 19 
         targetSdkVersion 19 
         versionCode 1 
         versionName '1.0' 
 dependencies {  
     compile 'com.google.android:multidex:0.1' 
     compile 'com.android.support:support-v4:19.0.1' 
     compile 'com.google.android.gms:play-services:5.0.77' 
     compile 'org.scala-lang:scala-library:2.11.2' 
     compile 'org.scaloid:scaloid_2.11:3.4-10' 
 }  

使用MutiDex的主意事项

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

Application类中逻辑的注意事项 :

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

 @Override 
  public void onCreate() {  
  super.onCreate();  
  final Context mContext = this;  
  new Runnable() {  
  @Override 
  public void run() {  
  // put your logic here! 
  // use the mContext instead of this here 
         }.run();  
     }  

二. 虽然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,你可能会见到如下的错误:

 Error:Execution failed for task ':app:dexDebug'.  
 > com.android.ide.common.internal.LoggedErrorException: Failed to run command:  
     $ANDROID_SDK/build-tools/android-4.4W/dx --dex --num-threads=4 --multi-dex  
   Error Code:  
   Output:  
     UNEXPECTED TOP-LEVEL EXCEPTION:  
     com.android.dex.DexException: Library dex files are not supported in multi-dex mode  
         at com.android.dx.command.dexer.Main.runMultiDex(Main.java:322)  
         at com.android.dx.command.dexer.Main.run(Main.java:228)  
         at com.android.dx.command.dexer.Main.main(Main.java:199)  
         at com.android.dx.command.Main.main(Main.java:103)  

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

 android {  
  // ... 
     dexOptions {  
         preDexLibraries = false 
 }  

OutOfMemoryError: Java heap space

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

 UNEXPECTED TOP-LEVEL ERROR:  
 java.lang.OutOfMemoryError: Java heap space  

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

 android {  
  // ... 
     dexOptions {  
         javaMaxHeapSize "2g" 
 }      

使用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

补充注意事项:

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

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

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

解决方案:

1. 修改library工程包名

 
推荐文章
强悍的斑马  ·  Android判断版本号 - J依情
2 月前
踢足球的火龙果  ·  微信小程序可以直接获取手机的安卓版本吗?ios直接获取版本信息判断兼容问题。 | 微信开放社区
2 月前
果断的水龙头  ·  解封时如何(以及在何处)修复android.os.BadParcelableException: ClassNotFoundException : RecyclerView$SavedState?-腾
1 月前
仗义的冲锋衣  ·  安卓SDK升级26ICON适配指导_aapt: error: <adaptive-icon> elements require a sd
1 月前
豪爽的烈马  ·  在android中找不到android.support.v4.content.FileProvider类开发者社区
1 月前
苦闷的猕猴桃  ·  燕理传媒李少华编剧作品《海边升起一座悬崖》获得戛纳短片金棕榈|戛纳电影节_网易订阅
1 年前
重感情的水桶  ·  女皇后宫有点乱 - 🌈️包子漫畫
1 年前
风流的日记本  ·  电梯操作证怎么考? - 知乎
1 年前
开朗的咖啡  ·  商务部行政处罚决定书(商法函[2018]12号)
1 年前
考研的猕猴桃  ·  Windows server 2012镜像下载_winserver2012镜像下载_miss you ya的博客-CSDN博客
1 年前
今天看啥   ·   Py中国   ·   codingpro   ·   小百科   ·   link之家   ·   卧龙AI搜索
删除内容请联系邮箱 2879853325@qq.com
Code - 代码工具平台
© 2024 ~ 沪ICP备11025650号