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

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

?

1
2
3
4
5
6
7
8
9
10
11
12
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文件。这里需要强调的是,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中的使用为例):

 

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

?

1
2
3
4
5
6
7
8
9
10
11
12
13
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
     }
}</filename>




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

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

?

1
2
3
4
5
6
7
8
9
10
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

?

1
2
3
4
5
6
7
repositories {
   jcenter()
}
 
dependencies {
   compile 'com.google.android:multidex:0.1'
}




接下来集成有两个步骤:

一. 从sdkextrasandroidsupportmultidexlibrarylibs 目录将android-support-multidex.jar导入工程中

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

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



?

1
2
3
4
5
6
7
public class MyApplication extends FooApplication {
     @Override
     protected void attachBaseContext(Context base) {
         super .attachBaseContext(base);
         MultiDex.install( this );
     }
}



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

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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文件中可能使用到的静态变量,可以根据如下所示的方式进行修改:

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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();
     }



常见问题

 

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

 

 

?

1
2
3
4
5
6
7
8
9
10
11
12
13
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:
     2
   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:

?

1
2
3
4
5
6
android {
     // ...
     dexOptions {
         preDexLibraries = false
     }
}


你可能感兴趣的:(Android 使用android-support-multidex解决Dex超出方法数的限制问题,)