Android开发中利用AndroidStudio分包生成多个dex文件

Android中单个dex文件所能包含的最大方法数是65536,这包含所依赖所有jar以及应用代码中的所有方法。简单的apk方法数很难达到这么多,但是对于一些复杂大型的应用来说65536就很容易超过,当方法数达到65536后,编译器就无法完成编译工作并抛出类似下面异常:

[plain]  view plain  copy
  1. FAILURE: Build failed with an exception.  
  2.   
  3. * What went wrong:  
  4. Execution failed for task ':app:transformClassesWithDexForDebug'.  
  5. > com.android.build.api.transform.TransformException: com.android.ide.common.process.ProcessException: java.util.concurrent.ExecutionException: com.android.dex.DexIndexOverflowException: method ID not in [0, 0xffff]: 65536  


[plain]  view plain  copy
  1. 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)  
还有可能方法数没有65536,也编译正常完成,但是应用安装在低版本时出现如下异常:

[plain]  view plain  copy
  1. E/dalvikm Optimization failed  
  2. E/installed: dexopt failed on '/data/dalvik-cache/[email protected]'  
出现这样情况是因为dexopt是一个程序,应用在安装时,系统会通过dexopt来优化dex文件,在优化过程中dexopt采用一个固定大小的缓冲区LinearAlloc来存储应用所有信息, LinearAlloc缓冲区在新版本Android系统中默认大小是8MB或16MB,但在Android2.2和2.3中只有5M,当待安装apk方法数较多时,尽管方法数没有达到65536,可能存储空间超过上限,这种情况dexopt程序就会报错,从而导致安装失败。

这样就出现了把一个dex拆分成多个dex,Google在2014年提出了multidex的解决方法,通过multidex可以很好解决方法数越界问题,下面就以AndroidStudio和Eclipse来说下具体怎么实现的,这篇先说说利用AndroidStudio拆分dex,Eclipse的用法后续更新

在Android5.0以前使用multidex需要引入Google提供的android-support-multidex.jar,这个jar在Android SDK目录下的“extras/android/support/multidex/library/libs”下,从5.0以后Android默认支持了multidex

1.修改工程中app目录下的build.gradle,在defaultConfig中添加multiDexEnabled true,如下

[html]  view plain  copy
  1. android {  
  2.     compileSdkVersion 25  
  3.     buildToolsVersion "25.0.2"  
  4.     defaultConfig {  
  5.         applicationId "multidex.jason.com.multidexdemo"  
  6.         minSdkVersion 15  
  7.         targetSdkVersion 25  
  8.         versionCode 1  
  9.         versionName "1.0"  
  10.         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"  
  11.         multiDexEnabled true  
  12.     }  
  13.     buildTypes {  
  14.         release {  
  15.             minifyEnabled false  
  16.             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'  
  17.         }  
  18.     }  
  19. }  
在dependencies中添加multidex依赖compile 'org.robolectric:shadows-multidex:3.3.1'

[html]  view plain  copy
  1. dependencies {  
  2.     compile fileTree(include: ['*.jar'], dir: 'libs')  
  3.     androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {  
  4.         exclude group: 'com.android.support', module: 'support-annotations'  
  5.     })  
  6.   
  7.     compile 'com.android.support:appcompat-v7:25.2.0'  
  8.     compile 'com.android.support.constraint:constraint-layout:1.0.2'  
  9.     compile 'org.robolectric:shadows-multidex:3.3.1'  
  10. }  
最终build.gradle文件就配置完成了,需要查看源码的可以在文章最后的链接中下载

2、在代码中支持multidex功能,具体有下面三种方式

1⃣️ 在AndroidManifest.xml中指定Application为MultiDexApplication,如下

[html]  view plain  copy
  1. <application  
  2.     android:name="android.support.multidex.MultiDexApplication"  
  3.     android:allowBackup="true"  
  4.     android:icon="@mipmap/ic_launcher"  
  5.     android:label="@string/app_name"  
  6.     android:roundIcon="@mipmap/ic_launcher_round"  
  7.     android:supportsRtl="true"  
  8.     android:theme="@style/AppTheme">  
2⃣️ 让应用的Application继承 MultiDexApplication

3⃣️如果不想让采用第2⃣️种,可以选择重写Application的attachBaseContext,这个方法比Application的onCreate要先执行,如下:

[java]  view plain  copy
  1. public class TestApplication extends Application {  
  2.     @Override  
  3.     protected void attachBaseContext(Context base) {  
  4.         super.attachBaseContext(base);  
  5.         MultiDex.install(this);  
  6.     }  
  7. }  
通过studio基本的工作都已经完成,采用了multidex方法后,只有当应用的方法数越界后,才会生成多个dex,具体打包多少个dex文件要看当前项目的规模了,下图是生成了两个dex的情况



如果想自动尝试的同学,在这里提供一个生成更多方法的类

[java]  view plain  copy
  1. public class ProductMethod {  
  2.   
  3.     public static void main(String[] args) {  
  4.         productMethod();  
  5.     }  
  6.   
  7.     private static void productMethod() {  
  8.         for (int i = 0; i < 10000; i++) {  
  9.             System.out.println("private void method" + i + "(){");  
  10.             if (i == 0) {  
  11.                 System.out.println("    method" + 9999 + "();");  
  12.             } else {  
  13.   
  14.                 System.out.println("    method" + (i - 1) + "();");  
  15.             }  
  16.             System.out.println("}");  
  17.         }  
  18.     }  
  19. }  
在输出的时,选择输出到文件,不要让打印到控制台;右键选中java类“Run as”-“Run Configurations”


这样复制到项目中某个Activity里就可以了,通过默认配置很容易就生成了多个dex文件。

当然还可以通过build.gradle文件中的一些配置项来定制dex生成过程。比如指定主dex文件所要包含的类,这个时候就可以通过--main-dex-list选项来实现这个功能,下面是修改后的build.gradle文件,在里面添加afterEvaluate和dependencies通缉,里面内容如下

[plain]  view plain  copy
  1. afterEvaluate {  
  2.     println("afterEvaluate")  
  3.     tasks.matching {  
  4.         it.name.startsWith('dex')  
  5.     }.each {dx ->  
  6.        def listFile = project.rootDir.absolutePath+'/app/maindexlist.txt'  
  7.         println("root dir:"+project.rootDir.absolutePath)  
  8.         println("dex task found :"+dx.name)  
  9.         if(dx.additionalParameters == null){  
  10.             dx.additionalParameters = []  
  11.         }  
  12.         dx.additionalParameters += '--multi-dex'  
  13.         dx.additionalParameters += '--main-dex-list='+listFile  
  14.         dx.additionalParameters += '--minimal-main-dex'  
  15.     }  
  16. }  
minimal-main-dex表明只有main-dex-list所指定类才能打包到主dex中,它的输入是一个文件maindexlist.txt,这里是在app根目录创建的,不一定非要在这创建,只要afterEvaluate配置地方和创建地方一致就行, maindexlist.txt具体格式如下:

[plain]  view plain  copy
  1. multidex/jason/com/multidexdemo/MainActivity.class  
  2.   
  3.   
  4. android/support/multidex/BuildConfig.class  
  5. android/support/multidex/MultiDex$V14.class  
  6. android/support/multidex/MultiDex$V19.class  
  7. android/support/multidex/MultiDex$V4.class  
  8. android/support/multidex/MultiDex.class  
  9. android/support/multidex/MultiDexApplication.class  
  10. android/support/multidex/MultiDexExtractor$1.class  
  11. android/support/multidex/MultiDexExtractor.class  
  12. android/support/multidex/ZipUtil$CentralDirectory.class  
  13. android/support/multidex/ZipUtil.class  

其中下面这几个是multidex的依赖的几个类,必须打包到主dex中,随着multidex的升级,可能也会有所改变;如果没有打包到主dex中,程序运行时会抛异常,无法找到multidex相关的类。另外需要注意的是Application的成员变量和代码块会先于attachBaseContext初始化执行,此时还没有其他dex文件被加载,会出现无法加载到对应的类而中止执行,在实际开发中要避免这样的错误,运行时会出现如下错误:

[plain]  view plain  copy
  1. E/AndroidRuntime:FATAL EXCEPTION: main  
  2. java.lang.NoClassDefFoundError:  
如果用使用其他Lib,要保证这些Lib没有被preDex,否则可能会抛出下面的异常
[plain]  view plain  copy
  1. UNEXPECTED TOP-LEVEL EXCEPTION:  
  2.     com.android.dex.DexException: Library dex files are not supported in multi-dex mode  
  3.         at com.android.dx.command.dexer.Main.runMultiDex(Main.java:337)  
  4.         at com.android.dx.command.dexer.Main.run(Main.java:243)  
  5.         at com.android.dx.command.dexer.Main.main(Main.java:214)  
  6.         at com.android.dx.command.Main.main(Main.java:106)  
遇到这个异常,需要在Gradle中修改,让它不要对Lib做preDexing

[plain]  view plain  copy
  1. android {  
  2. //  ...  
  3.     dexOptions {  
  4.         preDexLibraries = false  
  5.     }  
  6. }  
另外如果每次都打开MultiDex编译版本的话,会比平常用更多的时间(这个也容易理解,毕竟做了不少事情)
 Android的官方文档也给了我们一个小小的建议,利用Gradle建立两个Flavor.一个minSdkVersion设置成21,这是用了ART支持的Dex格式,避免了MultiDex的开销.而另外一个Flavor就是原本支持的最小sdkVersion.平时开发时候调试程序,就用前者的Flavor,发布版本打包就用后者的Flavor.

[plain]  view plain  copy
  1. android {  
  2.     productFlavors {  
  3.         // Define separate dev and prod product flavors.  
  4.         dev {  
  5.             // dev utilizes minSDKVersion = 21 to allow the Android gradle plugin  
  6.             // to pre-dex each module and produce an APK that can be tested on  
  7.             // Android Lollipop without time consuming dex merging processes.  
  8.             minSdkVersion 21  
  9.         }  
  10.         prod {  
  11.             // The actual minSdkVersion for the application.  
  12.             minSdkVersion 14  
  13.         }  
  14.     }  
  15.           ...  
  16.     buildTypes {  
  17.         release {  
  18.             runProguard true  
  19.             proguardFiles getDefaultProguardFile('proguard-android.txt'),  
  20.                                                  'proguard-rules.pro'  
  21.         }  
  22.     }  
  23. }  
apk是一个zip压缩包,dalvik每次加载apk都要从中解压出class.dex文件,加载过程还涉及到dex的classes需要的杂七杂八的依赖库的加载,耗时间。于是Android决定优化一下这个问题,在app安装到手机之后,系统运行dexopt程序对dex进行优化,将dex的依赖库文件和一些辅助数据打包成odex文件。存放在cache/dalvik_cache目录下。保存格式为apk路径 @ apk名 @ classes.dex。这样以空间换时间大大缩短读取/加载dex文件的过程。

dexopt程序的dalvik分配一块内存来统计你的app的dex里面的classes的信息,由于classes太多方法太多超过这个linearAlloc 的限制 ,减小dex的大小如下。

gradle脚本如下:

[plain]  view plain  copy
  1. android.applicationVariants.all {  
  2.     variant ->  
  3.         dex.doFirst{  
  4.             dex->  
  5.             if (dex.additionalParameters == null) {  
  6.                 dex.additionalParameters = []  
  7.             }  
  8.                 dex.additionalParameters += '--set-max-idx-number=48000'  
  9.   
  10.        }  
  11. }  
--set-max-idx-number= 用于控制每一个dex的最大方法个数,写小一点可以产生好几个dex。

Multidex方法虽然解决了方法数越界问题,也有些局限性,下面是可能出现的问题
1.应用安装到手机上的时候dex文件的安装是复杂的有可能会因为第二个dex文件太大导致ANR,需要用proguard优化你的代码。
2.使用了mulitDex的App有可能在4.0(api level 14)以前的机器上无法启动,因为Dalvik linearAlloc bug(Issue 22586)  ,用proguard优化你的代码将减少该bug几率。
3.使用了mulitDex的App在runtime期间有可能因为Dalvik linearAlloc limit (Issue 78035)  Crash。该内存分配限制在 4.0版本被增大,但是5.0以下的机器上的Apps依然会存在这个限制。
4.主dex被dalvik虚拟机执行时候,哪些类必须在主dex文件里面这个问题比较复杂。build tools 可以搞定这个问题。但是如果你代码存在反射和native的调用也不保证100%正确

源码地址

猜你喜欢

转载自blog.csdn.net/hehe_heh/article/details/80183017