转载来自http://xybcoder.github.io/2016/04/27/Multidex%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95%E6%95%B0%E8%B6%8A%E7%95%8C/
在Android 中单个dex文件所能包含的最大方法数为65536,这包含Android FrameWork、依赖的jar包以及应用本身代码中所有方法。65536是一个很大的数,对于一个小应用来说,它的方法数很难达到65536,但对于一些大型的应用来说,65536就很容易达到,当应用方法数超过65536时,编译器就无法完成编译工作并抛出异常:
1 2 3 4 5 6 7 8 9 10 11 12 | UNEXPECTED TOP-LEVEL EXCEPTION: com.android.dex.DexIndexOverflowException: method ID not in [0, 0xffff]: 65536 at com.android.dx.merge.DexMerger$6.updateIndex(DexMerger.java:502) at com.android.dx.merge.DexMerger$IdMerger.mergeSorted(DexMerger.java:277) at com.android.dx.merge.DexMerger.mergeMethodIds(DexMerger.java:491) at com.android.dx.merge.DexMerger.mergeDexes(DexMerger.java:168) at com.android.dx.merge.DexMerger.merge(DexMerger.java:189) at com.android.dx.command.dexer.Main.mergeLibraryDexBuffers(Main.java:454) at com.android.dx.command.dexer.Main.runMonoDex(Main.java:302) at com.android.dx.command.dexer.Main.run(Main.java:245) at com.android.dx.command.dexer.Main.main(Main.java:214) at com.android.dx.command.Main.main(Main.java:106) |
那么如何解决方法数越界问题?
首先我们想到的就是删除一些无用的代码和第三方库,但是很多情况下删除无用代码,方法数还是越界,针对这个问题,网上有很多解决办法:
1.编译打包时拆分dex
原理:
- 核心是将一个dex拆分成main.dex和多个second.dex(我这里为了讲解方面这样取名,实际为classes.dex和classes2.dex
…)。 - 使用gradle进行apk打包,同时需要依赖android-support-multidex.jar。
- 找出应用依赖小的第三方jar包,在build.gradle配置其编译class文件至second.dex中。这样打包完成后apk中就会出现多个dex。
- dalvik默认会加载main.dex,然后我们可以在main.dex的代码中手动加载second.dex至PathClassLoader。
优点: 这种方案代码改动最小,开发者工作量也小。
缺点: main.dex拆分难,往往只能缓解问题,且整个项目编译运行耗时依然长,最主要的是需要Android程序员的IDE全部切换成Android Studio或者Idea,然而对于无比偏爱Eclipse的程序员来说是难以接受的,尽管google官方宣布不再支持Eclipse。
2.开发时提前编译多个dex
原理:
- 第一步将项目中的代码提前生成jar包,最好是不常改动的代码。
- 将第三方jar包和第1步中生成的jar包,cmd执行SDK中的dx.bat命令,将jar包编译生成dex文件,比如v4.dex,v7.dex,map.dex等,最好一类功能或模块对应一个dex,然后将这些dex文件放进assets文件夹中。
- 将以上生成dex的jar包,做成编译环境Library。类似于android.jar,这个Library只参与主项目代码编译,不参与项目打包。这样项目就不会报错,能正常编译运行。
- 最后同第一种方案,手动加载assets文件中的dex文件,需要用到一个MultiDex.java文件的API。
优点: 能很大程度上解决65534的问题,且由于预先编译dex,所以项目在开发期间编译运行效率能提高很多。
缺点: 打包生成的apk体积依然庞大,且只能拆分项目里的公共模块和第三方jar包,所以对于业务和开发者庞大的项目来说,所有人在一个项目里开发协作起来依然存在问题,而且res资源文件依旧是个难以拆分的问题。
3.apk插件化动态加载
原理:
- 首先需要拆分项目代码,划分一个主模块和多个业务功能模块,每个模块相互独立,各个模块之间完全解耦。
- 主模块包含是应用的入口,需要包括主功能点和分模块的公共或依赖代码,还有一些第三方jar包或框架等(甚至能使用方案2的机制对主模块再做一个dex拆分)。
- 分模块在独立项目中独立编译运行,这时肯定有对主模块依赖的需求,所以同样需要一套编译环境Library的依赖库。同时分模块的res资源文件,同样拆分到分模块项目中。这样这个独立模块就能独立编译成apk文件了。
- 主模块中还需要一套完整的插件化框架,核心原理是使用反射实例化分模块中的Activity然后再使用一个伪Activity去代理这个实例化的Activity的生命周期和特性,对于res资源文件就需要自定义一个Context去处理了。
- 对于分模块apk的处理,防止在应用程序中的assets目录里也好,使用网络下载也可,不过当然是推荐后者啦!
优点: 各模块独立编译,项目拆分彻底。各模块只维护自己的代码和res文件,且支持不跨版本动态更新或fixbug,完成后几乎是程序员的春天了。
缺点: 首先最难的是项目模块的解耦和拆分,几乎是将整个项目翻了个底朝天,动作和代价非常大。其次是各子模块之间的相互调用和通信,还有一个是res文件的冲突和重复。最后是动态代理框架的编写,对技术要求非常高,从demo到成熟还需要一个不断优化的过程。
在这里详细说明第一种解决办法利用Google提供的android-support-multidex.jar这个jar包,它可以从apk中加载多个dex文件,从Android5.0以后,Android默认支持了multidex,Multidex方案主要是针对AndroidStudio和Gradle编译环境的。
现在开始解决问题吧:
在项目app目录下的build.gradle文件中的defaultConfig中添加
1
| multiDexEnabled true
|
转载自 http://xybcoder.github.io/2016/04/27/Multidex%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95%E6%95%B0%E8%B6%8A%E7%95%8C/
接下来在dependencies中添加multidex的依赖
1
| compile 'com.android.support:multidex:1.0.0'
|
经过上面的配置后,还需做另一项工作,那就是在代码中加入支持multidex的功能,这个功能比较简单,有三种方案可以选。
一、在manifest文件中指定Application为MultiDexApplication,如下所示:
1 2 3 4 5 6 | <application android:name="android.support.multidex.MultiDexApplication" android:lable="......" android:icon="......." .........../> <application> |
二、让应用的Application继承MultiDexApplication,如下所示:
1 2 | public class TestApplication extends MultiDexApplication{ } |
三、如果不想让应用的Application继承MultiDexApplication,还可以选择重写Application的attachBaseContext方法,这个方法比Application的onCreate要先执行,如下所示:
1 2 3 4 5 6 7 8 | public class TestApplication extends MultiDexApplication{ protected void attachBaseContext(Context context){ super.attachBaseContext(context); MultiDex.install(this); } } |
到这里第一种解决办法已经讲完了,第一种解决办法原理就是Gradle会在apk中打包2个或多个dex文件。
上面介绍的是multidex默认的配置,我们还可以通过build.gradle文件中一些其他配置项来定制dex文件的生成过程,在有些情况下,可能需要指定主dex文件所包含的类,这个时候可以通过–main-dex-list选项来实现这个功能。下面就是修改后的build.gradle文件,在build.gradle的里面添加了afterEvaluate区域,在afterEvaluate区域内部采用了–main-dex-list选项来指定主dex中包含的类,如下所示:
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 38 39 40 41 42 43 44 45 46 47 48 49 50 | apply plugin: 'com.android.application' android { compileSdkVersion 23 buildToolsVersion "23.0.2" defaultConfig { applicationId "com.xybcoder.note" minSdkVersion 19 targetSdkVersion 23 versionCode 1 versionName "1.0" multiDexEnabled true } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } afterEvaluate{ println "afterEvaluate" tasks.matching{ it.name.startsWith('dex') }.each{ dx-> def listFile=project.rootDir.absolutePath+'/app/maindexlist.txt' println "root dir:"+project.rootDir.absolutePath println "dex task found:"+dx.name if(dx.additionalParameters==null){ dx.additionalParameters=[] } dx.additionalParameters+='--multi-dex' dx.additionalParameters+='--main-dex-list='+listFile dx.additionalParameters+='--minimal-main-dex' } } dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.3.0' compile 'com.android.support:design:23.3.0' compile 'com.android.support:cardview-v7:23.3.0' compile 'com.jakewharton:butterknife:7.0.1' compile files('libs/lite-orm-1.5.1.jar') compile 'com.android.support:multidex:1.0.0' } |
注意:
maindexlist.txt指定了一些类,所有在maindexlist.txt指定的类都会打包到主dex中。它们格式是固定的,如下格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | com/xybcoder/multidextext/MainActivity.class com/xybcoder/multidextext/Login.class .......... //multidex这9个类必须打包到主dex中,否则会抛出异常 android/support/multidex/MultiDex.class android/support/multidex/MultiDexApplication.class android/support/multidex/MultiDexExtractor.class android/support/multidex/MultiDexExtractor$1.class android/support/multidex/MultiDex$V4.class android/support/multidex/MultiDex$V14.class android/support/multidex/MultiDex$V19.class android/support/multidex/ZipUtil.class android/support/multidex/ZipUtil$CentralDirectory.class |