Android 使用android-support-multidex解决Dex超出方法数的限制问题,让你的应用不再爆棚(转)

炒沙作縻终不饱,缕冰文章费工巧。这篇文章主要讲述Android 使用android-support-multidex解决Dex超出方法数的限制问题,让你的应用不再爆棚(转)相关的知识,希望能为你提供帮助。
如有转载,请声明出处: 时之沙:  http://blog.csdn.net/t12x3456      (来自时之沙的csdn博客)
                  随着应用不断迭代,业务线的扩展,应用越来越大(比如集成了各种第三方sdk或者公共支持的jar包,项目耦合性高,重复作用的类越来越多),相信很多人都遇到过如下的错误:


[java]  view plain  copy  

  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 使用android-support-multidex解决Dex超出方法数的限制问题,让你的应用不再爆棚(转)】              那么让我们看一下为什么会引起这种错误:
 
              在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的LinearAlloc存在着限制:  Android 2.2和2.3的缓冲区只有5MB,Android 4.x提高到了8MB或16MB。当方法数量过多导致超出缓冲区大小时,会造成dexopt崩溃,导致无法安装. 
        另外由于DEX文件格式限制,一个DEX文件中method个数采用使用原生类型short来索引文件中的方法,也就是最多表达65536个method,field/class的个数也均有此限制。对于DEX文件,则是将工程所需全部class文件合并且压缩到一个DEX文件期间,也就是Android打包的DEX过程中, 单个DEX文件可被引用的方法总数被限制为65536(自己开发以及所引用的Android Framework和第三方类库的代码).
            目前比较常用的方法:(1) 应用插件化,比如使用我正在参与开发的插件化框架 :  https://github.com/singwhatiwanna/dynamic-load-apk  ,如果有建议或者相关的问题,欢迎到Github上积极参与. (2) 分割Dex,多工程: 把所需要的.class文件或者是Jar文件和一些源码一起编译生成一个Jar文件。然后使用Android SDK提供的dx工具把Jar文件转成Dex文件。(可参考facebook:https://www.facebook.com/notes/facebook-engineering/under-the-hood-dalvik-patch-for-facebook-for-android/10151345597798920,这里边还可以看到在2.3上动态改变LinearAlloc缓冲的解决思路) 这两种方法并不冲突,插件化除了解决应用爆棚,还有很多其他的优点,可以看我之前的文章,不再复述.
              当然,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 plain  copy  
  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 plain  copy  
  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   

具体集成:
将如下配置加入工程  build.gradle中
 
[java]  view plain  copy  
  1. android  {   
  2.         defaultConfig  {   
  3.                 //  Enabling  multidex  support.   
  4.                 multiDexEnabled  true   
  5.         }   
  6. }   
  7. 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()方法:



[java]  view plain  copy  
  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 plain  copy  
    1. android  {   
    2.         compileSdkVersion  21   
    3.         buildToolsVersion  "21.1.0"   
    4.    
    5.         defaultConfig  {   
    6.                 ...   
    7.                 minSdkVersion  14   
    8.                 targetSdkVersion  21   
    9.                 ...   
    10.    
    11.                 //  Enabling  multidex  support.   
    12.                 multiDexEnabled  true   
    13.         }   
    14.         ...   
    15. }   
    16.    
    17. dependencies  {   
    18.     compile  ‘com.android.support:multidex:1.0.0‘   
    19. }   

















    推荐阅读