做为一名Android开发者,相信你对Android方法数不能超过65K的限制应该有所耳闻,随着应用程序功能不断的丰富,总有一天你会遇到一个异常:java
Conversion to Dalvik format failed:Unable toexecute dex: method ID not in [0, 0xffff]: 65536android
可能有些同窗会说,解决这个问题很简单,咱们只须要在Project.proterty中配置一句话就Ok啦,git
dex.force.jumbo=true
github
是的,加入了这句话,确实可让你的应用经过编译,可是在一些2.3系统的机器上很容易出现微信
INSTALL_FAILED_DEXOPT异常
app
对于以上两个异常,咱们先来分析一下缘由:eclipse
一、Android系统中,一个Dex文件中存储方法id用的是short类型数据,因此致使你的dex中方法不能超过65kide
二、在2.3系统以前,虚拟机内存只分配了5Mgradle
知道了缘由,咱们就来一个个的解决上面的问题,首先对于65k的问题,咱们在应用层是没法改变android系统的结构的,因此咱们没法将数据类型从short改变为int或者其余类型,也就是说一个dex中的方法数不能超过65k是咱们没法逾越的鸿沟,咱们只能减小一个dex中的方法数,首先最容易想到的方案就是去掉一些无用的Jar包,以及将一些属性设置为public,从而能够去掉get/set方法,这种方法只能临时解决问题,随着时间的推移,总有一天仍是会出现方法数超过65k的,毕竟一个应用通常是在加功能,不会减功能。优化
下面我来向你们介绍两种主流的解决方案,一种是以微信为表明的,将一些功能作成插件,动态加载,另外一种方案是以facebook为表明的分包方案,将一个apk中的dex文件分割成多个dex文件,而后动态的去加载dex文件。其实这两种方案的核心思想是同样的,插件是把将来要开发的新功能作成apk和dex动态加载,而分包方案是将已经完成的功能分红多个dex文件动态加载,其实我我的以为插件方案比分包方案更好的解决了65k的问题,由于插件方案不只可以解决65k问题,还能让咱们的应用体积减少,而分包只能解决65k的问题。
关于插件开发,作成动态加载,我在很早以前一篇文章中就写过其基本思想,有兴趣的同窗能够看看
《实现Android 动态加载APK(Fragment or Activity实现)》
http://blog.csdn.net/yuanzeyao/article/details/38565345
下面咱们重点介绍分包机制
咱们知道一个apk文件里面有一个dex文件,这个dex文件里面都是通过优化了的class文件,所谓分包,就是讲一个dex文件分红多个dex文件,这里咱们约定一下,第一个dex叫作main.dex,第二个叫作second.dex,一般在分包的时候,咱们须要将应用启动就须要使用的类放入到main.dex中,把不是立马就须要使用的类放入到second.dex中,对于Android系统,他只会默认加载main.dex的,second.dex对于他来讲可能只是一个资源文件,它是不会主动去加载second.dex,因此我在应用启动的过程当中,咱们须要为second.dex建立好一个类加载器,便于我在使用second.dex中的类时,可以里面加载该类。
关于如何加载second.dex也有好多作法,用的比较多的主要有一下几种
一、最简单的作法就是使用DexClassLoader进行加载,并将该DexClassLoader的父加载器设置为PathClassLoader
二、使用DexClassLoader加载,并将DexClassLoader的父加载器设置成PathClassLoader的父加载器,将PahtClassLoader的父加载器设置成DexClassLoader,仔细品味一下1和2的区别
三、将second.dex的路径放入到PathClassLoader的加载路径中
对于第2中方案,在有一种状况下是不能使用的,好比当second.dex经过DexClassLoader加载,可是second.dex中使用了一个类,这个类在main.dex中,这个时候就会抛出类找不到的异常,因此这种方案只能拥有second.dex不会用到main.dex类的时候
以上说的都是理论,下面咱们来实战一下
我这里会介绍两种方案,一种是基于gradle构建Android项目,一种是基于Ant构建Android项目
方案一:基于gradle构建Android项目,并实现分包
环境要求:AndroidStudio0.9以上,gradle插件0.14.2以上
一、若是你的工程在eclipse中,那么你须要将该工程导入到Android中,此时须要你升级adt22以上
二、打开你工程的build.gradle文件,检查gradle插件是不是0.14.2版本以后,由于0.14.2以后gradle插件才支持分包
三、打开工程下某一个Moudle的build.gradle文件,添加对android-support-multidex.jar的依赖
四、去掉第三方jar包中重复的类
五、设置虚拟机堆内存空间大小,避免在编译期间OOM
六、gradle构建项目时,貌似默认是不会将so库加入工程的,因此为了不此种状况发生,咱们须要制定so库目录,对于从eclipse转换过来的工程,还须要制定src和资源文件路径
七、若是你的项目依赖了其余库, 分别在各个库工程中加入 multiDexEnabled = true 和 jniLibs.srcDirs =['libs']两个配置便可
八、若是你的项目没有自定义Application,那么你在AndroidManifest.xml中使用MultiDexApplication便可,若是你的项目有自定义Application,而且是继承是Application,那么只须要改成继承MultiDexApplication便可,若是你的项目时继承的其余Application,那么你须要重写
attachBaseContext
通过上述配置,你的项目应该是已经成功分包了。若是分包成功,那么你解压你的apk文件,会发现有两个dex文件,经过上述的配置过程,咱们发现此方案咱们没法控制哪些类在main.dex中,哪些类在second.dex中,经过此种方案配置分包,能够兼容API4-API20.其加载second.dex采用的是上述方案中的3
下面咱们来看看基于Ant构建Android项目,并实现分包过程
在上述方案中,因为咱们没法看到gradle构建项目的脚本,因此咱们没法控制哪些类在第一个dex,哪些类在第二个dex,此方案中,咱们采用Ant构建,Ant是容许用户本身定义构建方案的,好比咱们能够经过自定义构建方案,将项目中某些第三方jar包放入到second.dex中,关于这个如何实现,请参考开源项目吧
https://github.com/mmin18/Dex65536.git
因为该项目加载second.dex所采用的方案是上述方案2,好比second.dex中的某些第三方jar包依赖main.dex中的某些类,这种方案就会实现,因此在此我将此方案去掉,换成了方案3,也就是将second.dex的路径设置到PathClassLoader的加载路径中
我只给出Android 4.4中的解决方案,其余系统大同小异
加载second.dex方法
分包成功后,解压apk文件,进入assert文件夹,咱们看到以下结构,libs.apk就是第三方jar编译后造成的dex文件
对于上面提到的第二个问题INSTALL_FAILED_DEXOPT,根本缘由就是2.3版本以前dalvik虚拟机的内存只有5M,因此不管是插件方案仍是分包方案在某些手机上仍是会遇到该问题,毕竟咱们仅仅是减小了每一个dex中包的数量,可是方法总数是没有减小的,因此解决此问题的根本方法就是修改虚拟机内存至8M,这个需求在Java层是没法实现,可是能够在c层实现,具体实现流程能够参考开源项目:
https://github.com/viilaismonster/LinearAllocFix.git
至于该方法中用到的一些方法,能够到android-support-multidex.jar中找到,这里就不都贴出来了,若是那里没有写清楚,欢迎留言讨论...
原创 :http://blog.csdn.net/yuanzeyao/article/details/41809423