首页 > 代码库 > Android 使用android-support-multidex解决Dex超出方法数的限制问题,让你的应用不再爆棚

Android 使用android-support-multidex解决Dex超出方法数的限制问题,让你的应用不再爆棚

         如有转载,请声明出处: 时之沙: http://blog.csdn.net/t12x3456    (来自时之沙的csdn博客)

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

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进行优化,这个过程有一个专门的工具来处理。叫DexOpt

DexOpt的运行过程是在第一次载入Dex文件的时候运行的。

这个过程会生成一个ODEX文件,即Optimised Dex。运行ODex的效率会比直接运行Dex文件的效率要高非常多。  可是在早期的Android系统中,DexOpt的LinearAlloc存在着限制: Android 2.2和2.3的缓冲区仅仅有5MB。Android 4.x提高到了8MB或16MB。

当方法数量过多导致超出缓冲区大小时,会造成dexopt崩溃,导致无法安装. 

        另外由于DEX文件格式限制。一个DEX文件里method个数採用使用原生类型short来索引文件里的方法,也就是最多表达65536个method,field/class的个数也均有此限制。对于DEX文件,则是将project所需全部class文件合而且压缩到一个DEX文件期间,也就是Android打包的DEX过程中。 单个DEX文件可被引用的方法总数被限制为65536(自己开发以及所引用的Android Framework和第三方类库的代码).

        眼下比較经常使用的方法:(1) 应用插件化,比方使用我正在參与开发的插件化框架 : https://github.com/singwhatiwanna/dynamic-load-apk ,假设有建议或者相关的问题,欢迎到Github上积极參与. (2) 切割Dex,多project: 把所须要的.class文件或者是Jar文件和一些源代码一起编译生成一个Jar文件。然后使用Android SDK提供的dx工具把Jar文件转成Dex文件。

(可參考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进行改动:
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
    }
}



可是默认的Dalvik 类载入器仅仅会寻找classes.dex,所以须要将它们进行合并才干使得被识别

当然。如今有了android.support.multidex.jar的支持,一切都会变得非常easy,首先我们看一下相关源代码的文件夹,详细的原理分析我会在之后的文章中进行解说:
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

详细集成:
将例如以下配置加入project build.gradle
 
android {
    defaultConfig {
        // Enabling multidex support.
        multiDexEnabled true
    }
}
dependencies {  compile ‘com.google.android:multidex:0.1‘}


MultiDex实现原理:

        Apk在运行的时候。有一个dexpathlist,而Multidex的源代码中,会依据你的系统版本号号对dexpathlist做改动。将全部的dex都加入到dexpathlist中.



接下来集成有两个步骤:

一. 从sdk\extras\android\support\multidex\library\libs 文件夹将android-support-multidex.jar导入project中

二. 假设你的project中已经含有Application类,那么让它继承android.support.multidex.MultiDexApplication类,

     假设你的Application已经继承了其它类而且不想做改动,那么还有第二种使用方式,覆写attachBaseContext()方法:



public class MyApplication extends FooApplication {
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        MultiDex.install(this);
    }
}


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

android {
    compileSdkVersion 21
    buildToolsVersion "21.1.0"

    defaultConfig {
        ...
        minSdkVersion 14
        targetSdkVersion 21
        ...

        // Enabling multidex support.
        multiDexEnabled true
    }
    ...
}

dependencies {
  compile ‘com.android.support:multidex:1.0.0‘
}





使用MutiDex的主意事项


一. 假设你继承了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仍有一些限制:

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


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 选项设置与预编译的libraryproject有冲突,因此假设你的应用中包括引用的liraryproject,须要将预编译设置为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"
    }
}    
        

使用eclipse的开发人员能够參考Google官方文档

https://developer.android.com/tools/building/multidex.html#mdex-gradle

须要安装支持gradle构建的插件,下载地址:

http://dist.springsource.com/release/TOOLS/gradle  (眼下可能须要FQ)



參考相关资料:

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


补充注意事项:

com.android.dex.DexException: Multiple dex files define L{package}/BuildConfig;
假设遇到这个错误请进行例如以下检查:

1. 主project与依赖libraryproject包名是否反复
2. 检查主project与依赖libraryproject是否含有反复的support.jar或者其它jar包


解决方式:

1. 改动libraryproject包名

2. 删除反复jar包

3.手工加入lib包,添加例如以下配置

dependencies {
            compile fileTree(dir: ‘libs‘, include: [‘*.jar‘])
            compile project(‘:lib-project-module‘)
        }




    

Android 使用android-support-multidex解决Dex超出方法数的限制问题,让你的应用不再爆棚