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


    转自: 时之沙: http://blog.csdn.net/t12x3456

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

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. UNEXPECTED TOP-LEVEL EXCEPTION:  
    2. java.lang.IllegalArgumentException: method ID not in [0, 0xffff]: 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中.



    接下来集成有两个步骤:

    一. 从sdkextrasandroidsupportmultidexlibrarylibs 目录将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.     }  



    常见问题

    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  (目前可能需要FQ) 

    参考相关资料:

    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.         }  
  • 相关阅读:
    MySQL——字符串拆分(含分隔符的字符串截取)
    一列数据组合成字符串并用逗号隔开
    web最大化,最小化文字错位的问题
    Esxi虚拟机安装Ros+Openwrt软路由双系统简单分享(踩到的坑,很大的坑)
    跟着老司机免费申请一个域名去!
    logging模块
    os.rename 和os.replace
    装饰器详解
    python if not
    globals和locals的区别
  • 原文地址:https://www.cnblogs.com/wangziqiang/p/4167071.html
Copyright © 2020-2023  润新知