您的位置:首页 > 移动开发 > Android开发

Mutildex解决Android应用Dex方法数限制

2016-11-22 10:35 274 查看
作为一个Android开发者,在开发应用时,随着业务规模发展到一定程度,不断地加入新功能,添加新的类库,代码在急剧的膨胀,相应的APK包的大小也在相应地增加。那么可能有一天,你的应用会抛出这样一个error信息:

Conversion to Dalvik format failed:
Unable to execute dex: method ID not in [0, 0xffff]: 65536


这个错误曾一度是Android应用开发者比较郁闷的问题,这个错误是Android应用的方法总数限制造成的。也就是普通Android应用的方法总数不能超过65536。还好,最近Google发布了新的Mutildex支持库,为方法总数超过65K的Android提供官方支持。有了Android的官方支持后,Mutildex比原先的解决方案简单多了。下面将介绍为什么会有这个65k的方法总数限制和如何在自己的应用中引用Google官方发布的Mutildex支持库来解决问题。

一、为什么会有这个dex方法数的限制

1、内部原因

在Android应用的编译过程中,Android开发工具会将资源文件,代码以及AndroidManifest.xml文件(包含应用的元数据)编译生成.apk文件,如下图所示:



从上面编译过程中可以看到Android应用在编译过程会产生一个.dex文件,当Android系统在安装一个应用的时候,有一步是对dex进行优化,这个过程有一个专门的工具来处理,叫DexOpt。Android从最早的Dalvik到现在Android5.0默认的ART运行时环境都能够执行这个.dex文件,他们都使用同一套指令集,即Dalvik指令集。而从官方http://source.android.com/devices/tech/dalvik/instruction-formats.html文档介绍中我们可以知道Dalvik指令集是使用16位寄存器来保存项目中所有的方法引用,包括第三方的方法。而DexOpt有一个问题,DexOpt会把每一个类的方法id检索起来,存在一个链表结构里面。但是这个链表的长度是用一个short类型来保存的,导致了方法id的数目不能超过65536个。这就意味着单个DEX文件可被引用的方法总数被限制为65536个。通常APK包含一个classes.dex文件,因此Android应用的方法总数不能超过这个数量,这包括Android框架,类库和自己开发的代码。尽管在新版本的Android系统中,DexOpt修复了这个问题,但是我们开发出来的应用仍然需要对低版本的Android系统做兼容。这就是Android Dex方法限制异常出现的原因,同时因为ART和Dalvik使用同一套指令集,这个限制在ART运行时环境中也会存在。

2、外部原因

第三方库里面有很多方法,比如Google Play Service,Guava库等,在很多大型复杂的Android应用开发中会用到Google Play Service,而Google Play Service5.0库里面就包含了将近20k方法,有时候可能还会用到Guava库,这个库包含将近14K方法,而单个.dex文件最多才允许65536个方法,如果引用了这两个google开源库,那么这两个库的方法数将近占了方法限制数目65536的一大部分了。再加上我们自己应用的方法就很容易导致方法数超出限制的范围。

二、Android应用如何打破65k方法数限制

首先对于Android 应用方法总数不能超过65K的问题,我们在应用层是无法改变Android系统的结构的。所以我们无法将数据类型从short改变为int或者其他类型,也就是说一个dex中的方法数不能超过65K是我们无法逾越的鸿沟。我们只能减少一个dex中的方法数,首先最容易想到的方案就是去掉代码中没有使用到的函数,去掉一些无用的Jar包,以及将一些属性由private换为设置成public,从而可以去掉get/set方法,这种方法只能临时解决问题,都是治标不治本的方法,随着时间的推移,总有一天还是会出现方法数超过65K的,毕竟一个应用一般是在加功能,不会减功能。那么有没有什么更好的发方法来解决这个Android应用方法超容的问题呢?答案是必须的,毕竟人类的智慧超级无敌嘛!

产生Android应用方法超限这个问题的根本原因是Android的单个.dex文件可被引用的方法总数限制为65536,如果超过这个方法数应用就会抛出方法超容的错误,所以解决问题要从根本出发,对症下药。所以可以通过将一个DEX文件拆分成多个DEX文件解决。Facebook介绍了为Android应用开发的Dalvik补丁https://www.facebook.com/notes/facebook-engineering/under-the-hood-dalvik-patch-for-facebook-for-android/10151345597798920,Android Developers博客介绍了通过自定义类加载过程http://android-developers.blogspot.hk/2011/07/custom-class-loading-in-dalvik.html的方法来解决此问题,但这些方法有些复杂,并不是我要讲的重点,这里不做介绍。还好,Android Developers在Google+上宣布了新的Mutildex支持库,为方法总数超过65K的Android应用提供官方支持。随着新的Mutildex支持库发布,Google正式为解决此问题提供官方支持。构建超过65K方法数的应用介绍了如何使用Gradle构建多DEX应用。官方文档链接:https://developer.android.com/tools/building/multidex.html ,下面介绍怎样在Android Studio上用Mutildex支持库。首先使用Android SDK Manager升级到最新的Android SDK Build Tools和Android Support Library R21.然后进行以下两步操作:

1. 修改Gradle,导入’com.android.support:multidex:1.0.0’,打开multidexEnabled;

apply plugin: 'com.android.application'

android {
compileSdkVersion 23
buildToolsVersion "22.0.1"

defaultConfig {
applicationId "com.meizu.graphics_demo"
minSdkVersion 14
targetSdkVersion 23
versionCode 1
versionName "1.0.0"
// Enabling multidex support.
multiDexEnabled true
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
dexOptions {
preDexLibraries = false
}
}

dependencies {
compile fileTree(dir: 'libs', include: [
4000
'*.jar'])
compile 'com.android.support:multidex:1.0.0'

}
}


2. 让应用支持多DEX文件

在MultidexApplication JavaDoc中描述了三种可选方法(这三个方法都可以实现,根据情况选择其中一种就可以了):链接:http://developer.android.com/reference/android/support/multidex/MultiDexApplication.html

1)在AndroidManifest.xml的application中声明android.support.multidex.MultidexApplication;

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

<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:name="android.support.multidex.MultiDexApplication"
android:label="@string/app_name"
android:theme="@style/AppTheme" >


2)如果你已经有自己的Application类,让其继承MultidexApplication;

public class AppTest extends MultiDexApplication {

@Override
public void onCreate() {
super.onCreate();
new Runnable() {
@Override
public void run() {
//具体逻辑实现
}
}.run();
}
}


3) 如果你的Application类已经继承自其它类,你不想或者不能修改它,那么可以重写attachBaseContext()方法;

public class AppTest extends Application {
@Override
public void onCreate() {
super.onCreate();
}

@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
}


经过上面的两个步骤后,Mutildex的配置到此就已经完成了,应用就可以实现多个DEX文件了。当应用构建时,构建工具会分析哪些类必须放在第一个DEX文件,哪些类可以放在附加的DEX文件里面。当它创建第一个DEX文件(classes.dex)后,如果有必要会继续创建附加的DEX文件,如classes2.dex,classes3.dex等。Mutildex的支持类库将被包含在应用的一个个DEX文件中,帮助实现对其他DEX文件的访问。(这里说明一下,如果应用的方法数没有超过单个dex文件限制的65536,即65K,那么apk中就只有默认的classes.dex文件。 PS:刚开始我以为只要用Mutildex后就会出先Android应用分包,结果运行apk后打开还只是一个classes.dex,刚开始我以为是自己配置Mutildex错误才不行,后来才想明白是我的Demo没有超出方法限制,后来我试着在我的demo中导入很多开源库,结果就分包了,出现了classes.dex和classes2.dex)运行后打开APK文件,结果如下图所示:



从上图中可以看出Mutildex支持库分包成功,分出classes.dex和classes2.dex两个DEX文件。

3.额外介绍一下mutildex使用中可能会遇到的问题

1)一些在二级Dex加载之前,可能会被调用到的类(比如静态变量的类),需要放在主Dex中.否则会ClassNotFoundError. 这时需要修改Gradle,可以显式的把一些类放在Main Dex中.

afterEvaluate {
tasks.matching {
it.name.startsWith('dex')
}.each { dx ->
if (dx.additionalParameters == null) {
dx.additionalParameters = []
}
dx.additionalParameters += '--multi-dex'
dx.additionalParameters += "--main-dex-list=$projectDir/multidex.keep".toString()
}
}


其中mutildex.keep其实是一个文本文件的文件名,存放在和这个Gradle脚本同一级的文件目录下,而这个文本文件的内容如下,实际就是把需要放在Main Dex的类罗列出来。

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


2)如果用使用其他Lib,要保证这些Lib没有被preDex,否则可能会抛出下面的异常。

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:337)
at com.android.dx.command.dexer.Main.run(Main.java:243)
at com.android.dx.command.dexer.Main.main(Main.java:214)
at com.android.dx.command.Main.main(Main.java:106)


如果遇到这个异常,需要在Gradle中修改,让它不要对Lib做preDexing。

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
dexOptions {
preDexLibraries = false
}
}


3)如果每次都打开MultiDex编译版本的话,会比平常用更多的时间, Android的官方文档也给了我们一个小小的建议,利用Gradle建立两个Flavor.一个minSdkVersion设置成21,这是用了ART支持的Dex格式,避免了MultiDex的开销.而另外一个Flavor就是原本支持的最小sdkVersion.平时开发时候调试程序,就用前者的Flavor,发布版本打包就用后者的Flavor.

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'
}


三、简单介绍一下Mutildex支持库的原理

假如我们的APK有classes.dex和classes2.dex两个DEX文件,在我们应用要兼容低版本的Android系统时,兼容包会在Applicaion实例化之后,检查系统版本是否支持 multidex,classes2.dex是否需要安装,如果应用需要安装则会从APK中解压出classes2.dex并将其拷贝到应用的沙盒目录下,通过Java反射机制将classes2.dex的Java反射出来注入到当前的classloader中。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android Mutildex Dex 分包
相关文章推荐