当我们工程版本迭代和业务需求越来越多时,代码量自然也就越来越多。所以在日常开发中会难免会碰到方法个数超过限制65535的错误。原因就是:Java源文件在打包成一个DEX文件,这个文件就是优化过的、Dalvik虚拟机可执行的文件,Dalvik虚拟机在执行DEX文件时,它使用了short这个类型索引DEX文件中的方法,这意味着单个DEX文件可以被定义的方法最多只能是65535个,当超过这个数量时就会发生编译错误。

我们在前面文章《Android Gradle使用详解(三) 之 Android Gradle插件配置详解》也简单提过,你可以通过dexOptions{}闭包里的jumboMode字段设置为true,从而忽略65535方法数限制的检查,但是只是忽略,如若你的apk明确不会运行在5.0以下的手机上的话,倒是没有问题,否则问题还是得解决。

解决方法超过65535最直接的方法就是删除代码,例如如果引用了大量的第三方aar或jar库的话,其实内部会有大量多余的代码,可以考虑修改它们,来删除无用的功能代码。还有就是我们在版本开发迭代过程中产生一些已经过时无用的功能,然后把这类代码删除。如果上述方法你都已经尝试过后还是存在65535方法超过限制的话,插件化也是解决此类问题的一种途径,但是插件化中的插件也有可能出现超过65535方法的情况啊,不可能又将插件再分多件插件,这样开发和维护成本显然很高。所以除此外,要彻底解决问题那就使用生成多个DEX文件,使每个DEX文件的方法数量都不超过65535。

对于Android 5.0及以上的手机,使用了ART的运行时方式,可以支持APP有多个DEX文件。ART在安装APP时执行预编译,把多个DEX文件合并成一个oat文件执行。但对于Android5.0以下手机的话,Dalvik虚拟机限制每个APP只能有一个class.dex,要使用它们,就得使用Android为提供的Mulidex库。例如:

android {
    ……
    defaultConfig {
        ……
        multiDexEnabled true
    }
    ……
}
dependencies {
    ……
    implementation 'com.android.support:multidex:1.0.2'
}

我们要想使用Multidex,那就得在defaultConfig{}、buildType{} 或 productFlavor{} 中给multiDexEnabled字段设置为true,这样就能开启Mulidex,使方法达到65535后生成多个DEX文件。但是前面提到Android 5.0以下手机只能有一个class.dex,所以我们要让虚拟机把生成的几个dex文件加载到一个class.dex中去。接着还要配置Mulidex库的依赖。因为我们我们要在Application中去处理它。

情况一、Mulidex提供了现成的Application其名字是MultiDexApplication,如果我们工程中没有自定义Application的话,那直接在AndroidManifest.xml中配置它即可,如:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.zyx.myapplication">

    <application
        android:name="android.support.multidex.MultiDexApplication"
        ……>
        ……
    </application>
     ……
</manifest>

情况二、如果本来就存在自定义的Application,那么使自定义的Application继承MultiDexApplication也是可以的,如:

package com.zyx.myapplication;

import android.support.multidex.MultiDexApplication;

public class MyApplication extends MultiDexApplication {
    ……
}

情况三、又如果本来就存在自定义的Application,而又已经有继承的其他功能的Application的话,可以通过重写attachBaseContext方法也是可以的:

package com.zyx.myapplication;

import android.app.Application;
import android.content.Context;
import android.support.multidex.MultiDex;

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

attachBaseContext是Application创建后最先回调的方法,我们在此方法内调用一句代码:MultiDex.install(this);即可,因为从源码可以了解到这是和继承MultiDexApplication是一样的。我们在开发过程中建议使用最后情况三的方式来处理,因为这样可以方便的进行Android SDK 版本的判定,若果是Android 5.0以下情况才调用MultiDex.install(this);这行代码,就如:

package com.zyx.myapplication;

import android.app.Application;
import android.content.Context;
import android.os.Build;
import android.support.multidex.MultiDex;

public class MyApplication extends Application {
    @Override
    protected void attachBaseContext(Context context) {
        super.attachBaseContext(context);
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            MultiDex.install(this);
        }
    }
}

指定拆分

某些情况下,我们在App启动后要第一时间处理某些逻辑,但是刚好要处理的逻辑所在类没有放在主的dex文件中,这时刚好classex2.dex还没有加载完成,这时就会产生类找到不的异常。这时就可以通过Gradle中multiDexKeepProguard或multiDexKeepFile属性来进行配置指写的类留在主的dex文件中。使用如下:

android {
    ……
    defaultConfig {
        ……
        multiDexEnabled true
        multiDexKeepFile file('multiDexKeep.txt')
        //或 multiDexKeepProguard file('multidex.pro')
    }
    ……
}
dependencies {
    ……
    implementation 'com.android.support:multidex:1.0.2'
}

multiDexKeepFile属性接收的参数multiDexKeep.txt文件用于指写保留的类列表,格式如:

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$4.class
android/support/multidex/MultiDex$14.class
android/support/multidex/MultiDex$19.class
android/support/multidex/ZipUtil.class
android/support/multidex/ZipUtil$CentralDirectory.class

multiDexKeepProguard属性接收的参数multidex.pro文件以Proguard的方式指定保留的类,格式如:

-keep class android.support.multidex.** {
    *;
}

编译时,相应的文件文件会加入到

build/intermediates/multi-dex/debug/maindexlist.txt


总结

  1. Dalvik虚拟机在执行DEX文件时,它使用了short这个类型索引DEX文件中的方法,所以DEX文件可以被定义的方法最多只能是65535个
  2. Android程序打包时,Android Gradle会自动判断工程中方法是否有超过65535个,如果没有超过会正常地生成一个classes.dex文件;如果超过了就会报出65535的编译错误
  3. 我们可以通过Mulidex来解决65535方法超过的问题,它会在超过方法数时就会生成1classes.dex文件和若干个附属的DEX文件,如classes2.dex…
  4. dex安装在机器上的过程比较复杂,在App启动时加载额外的dex会使启动速度下降,而且如果dex体积较大时可能会在性能够差的机器上造成ANR,虽然Mulidex可以解决65535的问题,但是我们在平时写代码时还是要注意尽量避免滥用第三方库和避免冗余代码。
  5. 若要解决加载卡顿或ANR印象,可以考虑将 MultiDex.install(this); 放置在异步线程去执行,但是要处理好在加载完成前,保证classes2.dex里的类不会被提前调用到。

 

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐