在 Andorid 中单个 dex 文件所能包含的最大方法数为 65536,这包含 Android FrameWork、依赖的 jar 包以及应用本身的代码中所有的方法,65536 是一个很大的数字,一般来说一个简单的应用很难达到 65536 这个方法数,但是一些比较大型的应用以及大家开始越来越多的使用组件化开发,65536 就很容易达到,当应用的方法达到 65536 后,编译器就无法完成编译,会抛出异常,今天我们就来一起看看如何来解决方法数越界这个问题
一、问题所报异常
我们来看一下当方法数超出以后编译器所报的异常:
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:168)
at com.android.dx.command.dexer.Main.mergeLibraryDexBuffers(Main.java:189)
at com.android.dx.command.dexer.Main.runMonoDex(Main.java:454)
at com.android.dx.command.dexer.Main.run(Main.java:303)
at com.android.dx.command.dexer.Main.main(Main.java:215)
at com.android.dx.command.Main.main(Main.java:106)
相信大家有过方法数越界问题的对上面所报的错误应该是比较熟悉的,还有一种情况有所不同,有时候方法数并没有达到 65536 并且编译器也正常的完成了编译工作,但是应用在低版本安装时异常中止,异常信息如下:
E/dalvikvm:Optimization failed
E/dexopt failed on '/data/dalvik - cache/data@app@com.ryg.multidextest - 2.apk@classes.dex' res =65280
为什么会出现这样的情况那,其实是这样的,dexopt 是一个程序,应用在安装时,系统会通过 dexopt 来优化 dex 文件,在优化过程中 dexopt 采用一个固定大小的缓冲区来存储应用中所有的方法信息,这个缓冲区就是 LinearAlloc,LinearAlloc 缓冲区在新版本的 Andriod 系统中,其大小是 8MB 或 16MB,但是在 2.2 和 2.3 中却只有 5MB,当待安装的 apk 中的方法数比较多时,尽管他还没有达到 65536 这个方法数的上限,但是它的存储空间仍然有可能超出 5MB 这种情况下 dexopt 程序就会报错,从而导致安装失败,这种情况主要在 2.X 系统的手机上出现,可以看大不管是编译时方法数越界还是安装时的 dexopt 错误,它们都给开发过程带来很大的困扰
二 、问题如何解决
1)首先我们应该删除无用的代码和第三方库,这是我们首先需要考虑的,但是可能在大多数情况下,即使我们删除了无用的代码和库,方法数仍然越界,之前大家会通过插件化的机制来动态加载部分 dex,通过将一个 dex 拆分成多个 dex,这就在一定程度上解决了方法数越界的问题,但是插件化是一套重量级的方案,并且其兼容性问题较多,从单纯解决方法数越界问题来说,插件化并不是一个非常适合的方案
2)为了解决这个问题,Google 在 2014 年提出 multidex 的解决方案,通过 multidex 可以很好地解决方法数越界的问题,并且使用起来非常简单
在 Android 5.0 以前使用 multidex 需要引入 Google 提供的 android - support - multidex.jar 这个 jar 包,这个 jar 包可以在 Android SDK 目录下的
extras/android/support/multidex/library/libs 下面找到,从 Android 5.0 开始,Android 默认支持了 multidex 它可以从 apk 中加载多个 dex 文件,
Multidex 方法主要是针对Android Studio 和 Gradle 编译环境的,如果是 Eclipse 和 Ant 就会更复杂,而且由于 Android Studio 作为官方 IDE
已经完全替代了 Eclipse ADT,所以我们接下来只介绍 AndroidStudio 如何来配置,就不在介绍 Eclipse 如何配置了
三、在 Andorid Studio 中进行配置
在 Android Studio 和 Gradle 编译环境中,如果要使用 multidex,首先要使用 Andorid SDK Build Tools 21.1 及以上版本,接着修改工程目录中
app 目录下的 build.gradle 文件,在deflautConfig 闭包中添加 multiDexEnabled true 这个配置项,如下所示,如果你对 Andorid Studio 的 Gradle
还不是很了解 可以先阅读如下文章,文章对 build.gradle 做了详细的解答:
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion "25.0.3"
defaultConfig {
applicationId "com.example.qiudengjiao.multidex"
minSdkVersion 15
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
//在这里添加 multidex support
multiDexEnabled true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.3.1'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
testCompile 'junit:junit:4.12'
}
接着我们还需要在 dependencies 中添加 multidex 的依赖
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.3.1'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
testCompile 'junit:junit:4.12'
//添加如下multidex依赖
compile 'com.android.support:multidex:1.0.1'
}
最终完成配置的 build.gradle 文件如下所示:
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion "25.0.3"
defaultConfig {
applicationId "com.example.qiudengjiao.multidex"
minSdkVersion 15
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
//在这里添加 multidex support
multiDexEnabled true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.3.1'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
testCompile 'junit:junit:4.12'
//添加如下multidex依赖
compile 'com.android.support:multidex:1.0.1'
}
经过上面的过程,还需要在代码中加入支持 multidex 的功能,这个过程是比较简单的,有 3 种方案可选
1)第一种方案
在 AndroidManifest.xml 文件中指定 Application 为 MultiDexApplication 如下所示:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.qiudengjiao.multidex">
<application
android:name="android.support.multidex.MultiDexApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
2)第二种方案
让应用的 Application 继承 MultiDexApplication 例如:
public class TestApplication extends MultiDexApplication {
}
3)第三种方案
如果不想让应用的 Application 继承 MultiDexApplication,还可以选择重写 Application 的 attachBaseContext 方法,这个方法比 Application 的onCreate 要先执行,如下所示:
public class TestApplication extends Application{
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
}
现在所有工作就完成了,可以发现应用不但可以编译通过,还可以在 Andorid 2.x 手机上正常安装,可以发现,multidex 使用起来还是非常方便,对于一个使用 multidex 方案的应用,采用了上面的配置项,如果这个这个方法数没有越界,那么 Gradle 并不会生成多个 dex 文件,如果方法数越界后,Gradle 就会在 apk 中打包 2 个或多个 dex 文件,具体打包多少个 dex 文件要看当前项目的代码规模