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

[java]  view plain copy
  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的部分工作,从而加快加载的过程.(可参考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/".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  

具体集成:
将如下配置加入工程  classPath
  [java]  view plain copy
  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 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. }  





使用MutiDex的主意事项


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

Application类中逻辑的注意事项:

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

[java]  view plain copy
  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 plain copy
  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 plain copy
  1. android {  
  2.     // ...  
  3.     dexOptions {  
  4.         preDexLibraries = false  
  5.     }  
  6. }  



OutOfMemoryError: Java heap space

当运行时如果看到如下错误:
[java]  view plain copy
  1. UNEXPECTED TOP-LEVEL ERROR:  
  2. java.lang.OutOfMemoryError: Java heap space  

dexOptions 中有一个字段用来增加java堆内存大小:
[java]  view plain copy
  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 plain copy
  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 plain copy
  1. dependencies {  
  2.             compile fileTree(dir: 'libs', include: ['*.jar'])  
  3.             compile project(':lib-project-module')  
  4.         } 


补充注意事项:

[java]  view plain copy
  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 plain copy
  1. dependencies {  
  2.             compile fileTree(dir: 'libs', include: ['*.jar'])  
  3.             compile project(':lib-project-module')  
  4.         } 

出处: 时之沙:  http://blog.csdn.net/t12x3456  



更多相关文章

  1. 全面认识Android中Gradle相关配置文件
  2. Android APK开发 Drawable文件夹下的自定义Drawable文件
  3. Android 应用语言切换的三种方法
  4. Android CheckBox中设置padding无效问题解决方法
  5. Android项目文件结构
  6. Android设备Root检测方法
  7. Android Studio中创建Selector文件的方法
  8. blcr加速android启动速度遇到的问题及解决方法
  9. Android文件读写实例代码

随机推荐

  1. android 系统级 bug audio latency
  2. Android(安卓)jni系统变量、函数、接口定
  3. Android之UI学习篇七:ImageView实现适屏和
  4. Android中设置定时器
  5. Android(安卓)各个版本WebView
  6. Android(安卓)UI布局中设置了fill_parene
  7. Android(安卓)博客园客户端 (二) 新界面&部
  8. GestureDetector.OnGestureListener 详解
  9. Android(安卓)常用开发工具类
  10. [Android(安卓)L]关于Android(安卓)L的Se