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

来源:互联网 发布:星野竜一 知乎 编辑:程序博客网 时间:2024/05/21 10:01

随着应用不断迭代,业务线的扩展,应用越来越大(比如集成了各种第三方sdk或者公共支持的jar包,项目耦合性高,重复作用的类越来越多),相信很多人都遇到过如下的错误:java.lang.IllegalArgumentException: method ID not in [0, 0xffff]: 65536 ; 没错,你的应用中的Dex 文件方法数超过了最大值65536的上限。为此谷歌官方推出了multidex兼容包,配合AndroidStudio实现了一个APK包含多个dex的功能。

MultiDex实现原理:

Apk在运行的时候,有一个dexpathlist,而Multidex的源码中,会根据你的系统版本号对dexpathlist做修改,将所有的dex都添加到dexpathlist中。

集成方法:

1、将如下配置加入工程 build.gradle中:

android {    compileSdkVersion 21    buildToolsVersion "21.1.0"    defaultConfig {        ...        minSdkVersion 14        targetSdkVersion 21        ...        // Enabling multidex support.        multiDexEnabled true    }    dexOptions {//dex配置          javaMaxHeapSize "4g"          preDexLibraries = false          additionalParameters = [//dex参数详见 dx --help                                  '--multi-dex',//多分包                                  '--set-max-idx-number=60000',//每个包内方法数上限                                  '--main-dex-list='+projectDir+'/maindexlist.txt',//打包进主classes.dex的文件列表                                  '--minimal-main-dex'//使上一句生效                                  ]      }      ...}dependencies {  compile ‘com.android.support:multidex:1.0.0‘}

2、继承android.support.multidex.MultiDexApplication类:

  • 如果你的工程中已经含有Application类,那么让它继承android.support.multidex.MultiDexApplication类;
  • 如果你的Application已经继承了其他类并且不想做改动,那么还有另外一种使用方式,覆写attachBaseContext()方法:
public class MyApplication extends FooApplication {      @Override      protected void attachBaseContext(Context base) {          super.attachBaseContext(base);          MultiDex.install(this);      }  } 

使用注意事项:

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

Application类中逻辑的注意事项:

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

@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();  }  

二. 虽然Google解决了应用总方法数限制的问题,但并不意味着开发者可以任意扩大项目规模。Multidex仍有一些限制:

  • DEX文件安装到设备的过程非常复杂,如果第二个DEX文件太大,可能导致应用无响应。此时应该使用ProGuard减小DEX文件的大小。

  • 由于Dalvik linearAlloc的Bug,应用可能无法在Android 4.0之前的版本启动,如果你的应用要支持这些版本就要多执行测试。

  • 同样因为Dalvik linearAlloc的限制,如果请求大量内存可能导致崩溃。Dalvik linearAlloc是一个固定大小的缓冲区。在应用的安装过程中,系统会运行一个名为dexopt的程序为该应用在当前机型中运行做准备。dexopt使用LinearAlloc来存储应用的方法信息。Android 2.2和2.3的缓冲区只有5MB,Android 4.x提高到了8MB或16MB。当方法数量过多导致超出缓冲区大小时,会造成dexopt崩溃。

  • 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,你可能会见到如下的错误:

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:

android {      // ...      dexOptions {          preDexLibraries = false      }  }  

OutOfMemoryError: Java heap space

当运行时如果看到如下错误:

UNEXPECTED TOP-LEVEL ERROR:  java.lang.OutOfMemoryError: Java heap space  

在dexOptions中有一个字段用来增加java堆内存大小:

android {      // ...      dexOptions {          javaMaxHeapSize "2g"      }  }      

Multidex的方式的局限性:

虽然我们开起来multidex是一个极好的东西,但是multidex还是存在自己的局限性,我们在开发测试之前要清楚局限性是什么:

1、如果二DEX文件太大,安装分割dex文件是一个复杂的过程,可能会导致应用程序无响应(ANR)的错误。在这种情况下,你应该尽量的减小dex文件的大小和删除无用的逻辑,而不是完全依赖于multidex。

2、在Android 4.0设备(API Level 14)之前,由于Dalvik linearalloc bug(问题22586),multidex很可能是无法运行的。如果希望运行在Level 14之前的Android系统版本,请先确保完整的测试和使用。

3、应用程序使用了multiedex配置的,会造成使用比较大的内存。当然,可能还会引起dalvik虚拟机的崩溃(issue 78035)。

4、对于应用程序比较复杂的,存在较多的library的项目。multidex可能会造成不同依赖项目间的dex文件函数相互调用,找不到方法。

优化multidex开发和构建:

一个multidex的配置,对系统apk的构建、签名、打包复杂性大大的增加。这就意味着,你每一次的构建过程都是相当耗时的。

为了加快我们的开发速度,加快构建的过程,我们可以在Gradle productFlavors新建出来一个 development flavor 和 production flavor 来满足我们不同构建需求。

下面是一个列子演示我们如何设置这些flavors在Gradle build文件中:

android {    productFlavors {        // Define separate dev and prod product flavors.        dev {            // dev utilizes minSDKVersion = 21 to allow the Android gradle plugin            // to pre-dex each module and produce an APK that can be tested on            // Android Lollipop without time consuming dex merging processes.            minSdkVersion 21        }        prod {            // The actual minSdkVersion for the application.            minSdkVersion 14        }    }          ...    buildTypes {        release {            runProguard true            proguardFiles getDefaultProguardFile(‘proguard-android.txt‘),                                                 ‘proguard-rules.pro‘        }    }}dependencies {  compile ‘com.android.support:multidex:1.0.0‘}

在你完成了伤处的配置修改之后,你配置productFlavor 和 buildType来使用 ,devDebug 变种app。使用这些变种app,可以设置proguard disable、multidex enable方便我们测试。

这些配置需要针对Android Gradle插件做如下操作:

1、在分包前,编译应用程序中的每一个module包括依赖项目,这个步骤称为 pre-dexing。
2、include每一个dex文件
3、最重要的是,对于主dex文件,不会做切分。以保证计算速度。

这样设置既能够保证我们的最终报是一个使用了multidex模式的,而又不影响我们平时开发的测试效率。

原文链接1
原文链接2

0 0