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

来源:互联网 发布:mac怎么使用谷歌浏览器 编辑:程序博客网 时间:2024/05/20 16:36

作为一个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: ['*.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/classandroid/support/multidex/MultiDex/V14/classandroid/support/multidex/MultiDex/V19/classandroid/support/multidex/MultiDex/V4/classandroid/support/multidex/MultiDex/classandroid/support/multidex/MultiDexApplication/classandroid/support/multidex/MultiDexExtractor/1/classandroid/support/multidex/MultiDexExtractor/classandroid/support/multidex/ZipUtil/CentralDirectory/classandroid/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中。

0 0