彻底解决Android 应用方法数不能超过65K的问题
作为一名Android开发者,相信你对Android方法数不能超过65K的限制应该有所耳闻,随着应用程序功能不断的丰富,总有一天你会遇到一个异常:
Conversion to Dalvik format failed:Unable toexecute dex: method ID not in [0, 0xffff]: 65536
可能有些同学会说,解决这个问题很简单,我们只需要在Project.proterty中配置一句话就Ok啦,
dex.force.jumbo=true
是的,加入了这句话,确实可以让你的应用通过编译,但是在一些2.3系统的机器上很容易出现
INSTALL_FAILED_DEXOPT异常
对于以上两个异常,我们先来分析一下原因:
1、Android系统中,一个Dex文件中存储方法id用的是short类型数据,所以导致你的dex中方法不能超过65k
2、在2.3系统之前,虚拟机内存只分配了5M
知道了原因,我们就来一个个的解决上面的问题,首先对于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也有好多做法,用的比较多的主要有一下几种
1、最简单的做法就是使用DexClassLoader进行加载,并将该DexClassLoader的父加载器设置为PathClassLoader
2、使用DexClassLoader加载,并将DexClassLoader的父加载器设置成PathClassLoader的父加载器,将PahtClassLoader的父加载器设置成DexClassLoader,仔细品味一下1和2的区别
3、将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以上
1、如果你的工程在eclipse中,那么你需要将该工程导入到Android中,此时需要你升级adt22以上
2、打开你工程的build.gradle文件,检查gradle插件是否是0.14.2版本之后,因为0.14.2之后gradle插件才支持分包
3、打开工程下某一个Moudle的build.gradle文件,添加对android-support-multidex.jar的依赖
4、去掉第三方jar包中重复的类
5、设置虚拟机堆内存空间大小,避免在编译期间OOM
6、gradle构建项目时,貌似默认是不会将so库加入工程的,所以为了避免此种情况发生,我们需要制定so库目录,对于从eclipse转换过来的工程,还需要制定src和资源文件路径
7、如果你的项目依赖了其他库, 分别在各个库工程中加入 multiDexEnabled = true 和 jniLibs.srcDirs =['libs']两个配置即可
8、如果你的项目没有自定义Application,那么你在AndroidManifest.xml中使用MultiDexApplication即可,如果你的项目有自定义Application,并且是继承是Application,那么只需要改为继承MultiDexApplication即可,如果你的项目时继承的其他Application,那么你需要重写
attachBaseContext
- @Override
- protected void attachBaseContext(Context base) {
- super.attachBaseContext(base);
- MultiDex.install(this);
- }
经过上述配置,你的项目应该是已经成功分包了。如果分包成功,那么你解压你的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方法
- /**
- @param loader
- PathClassLoader
- @additionalClassPathEntries
- 要被加载的dex文件,这里就是我们的second.dex
- @optimizedDirectory
- 就是dex文件解压的目录
- */
- private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
- File optimizedDirectory)
- throws IllegalArgumentException, IllegalAccessException,
- NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
- /* The patched class loader is expected to be a descendant of
- * dalvik.system.BaseDexClassLoader. We modify its
- * dalvik.system.DexPathList pathList field to append additional DEX
- * file entries.
- */
- //通过反射找到pathList的值
- Field pathListField = findField(loader, "pathList");
- Object dexPathList = pathListField.get(loader);
- ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
- //将second.dex 加入到PathClassLoader的加载路径中
- expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,
- new ArrayList<File>(additionalClassPathEntries), optimizedDirectory,
- suppressedExceptions));
- if (suppressedExceptions.size() > 0) {
- for (IOException e : suppressedExceptions) {
- Log.w(TAG, "Exception in makeDexElement", e);
- }
- Field suppressedExceptionsField =
- findField(loader, "dexElementsSuppressedExceptions");
- IOException[] dexElementsSuppressedExceptions =
- (IOException[]) suppressedExceptionsField.get(loader);
- if (dexElementsSuppressedExceptions == null) {
- dexElementsSuppressedExceptions =
- suppressedExceptions.toArray(
- new IOException[suppressedExceptions.size()]);
- } else {
- IOException[] combined =
- new IOException[suppressedExceptions.size() +
- dexElementsSuppressedExceptions.length];
- suppressedExceptions.toArray(combined);
- System.arraycopy(dexElementsSuppressedExceptions, 0, combined,
- suppressedExceptions.size(), dexElementsSuppressedExceptions.length);
- dexElementsSuppressedExceptions = combined;
- }
- suppressedExceptionsField.set(loader, dexElementsSuppressedExceptions);
- }
- }
分包成功后,解压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
彻底解决Android 应用方法数不能超过65K的问题的更多相关文章
- 解决Android 应用方法数不能超过65K的问题
Conversion to Dalvik format failed:Unable toexecute dex: method ID not in [0, 0xffff]: 65536 假设你的应用出 ...
- Android为什么方法数不能超过65535
言归正传,来聊聊为什么方法数不能超过65535?搬上Dalvik工程师在SF上的回答,因为在Dalvik指令集里,调用方法的invoke-kind指令中,method reference index只 ...
- Android方法数不能超过65535
为什么方法数不能超过65535?搬上Dalvik工程师在SF上的回答,因为在Dalvik指令集里,调用方法的invoke-kind指令中,method reference index只给了16bits ...
- Android的方法数超过65535问题
Under the Hood: Dalvik patch for Facebook for Android 先来看一段中文内容 Hack Dalvik VM解决Android 2.3 DEX/Line ...
- Android工程方法数超过64k,The number of method references in a .dex file cannot exceed 64K.
最近将一个老的Eclipse项目转到Android Studio后,用gradle添加了几个依赖,项目可以make,但是一旦run就报错 Error:The number of method refe ...
- Android方法数methods超过65536
当Android App中的方法数超过65535时,如果往下兼容到低版本设备时,就会报编译错误: Cannot fit requested classes in a single dex file. ...
- Android 使用android-support-multidex解决Dex超出方法数的限制问题,让你的应用不再爆棚(转)
如有转载,请声明出处: 时之沙: http://blog.csdn.net/t12x3456 (来自时之沙的csdn博客) 随着应用不断迭代,业务线的扩展,应用越来越大(比如集成了各种第三方sd ...
- Android 使用android-support-multidex解决Dex超出方法数的限制问题
随着应用不断迭代,业务线的扩展,应用越来越大(比如集成了各种第三方sdk或者公共支持的jar包,项目耦合性高,重复作用的类越来越多),相信很多人都遇到过如下的错误: UNEXPECTED TOP-LE ...
- Android 65536方法数限制的思考
前言 没想到,65536真的很小. 1 Unable to execute dex: method ID not in [0, 0xffff]: 65536 PS:本文只是纯探索一下这个65K的来源, ...
随机推荐
- 8、泛型程序设计与c++标准模板库5.函数对象
1.函数对象 函数对象是STL提供的第四类主要组件,它使得STL的应用更加灵活方便,从而增强了算法的通用性.大多数STL算法可以用一个函数对象作为参数.所谓“函数对象”其实就是一个行为类似函数的对象, ...
- ES Docs-3:Modifying Data
Modifying Data Indexing/Replacing Documents curl -XPUT 'localhost:9200/customer/external/1?pretty' - ...
- 【service调用dao层传参的三种方式】
第一种方案:默认数组角标: service Public User selectUser(String name,String area); mapper: <select id="s ...
- 洛谷P1034 矩形覆盖
P1034 矩形覆盖 题目描述 在平面上有 n 个点(n <= 50),每个点用一对整数坐标表示.例如:当 n=4 时,4个点的坐标分另为:p1(1,1),p2(2,2),p3(3,6),P4( ...
- 快速枚举的迭代器类NSEnumerator
另外,OC中有一个专门的快速枚举的迭代器类NSEnumerator,这个类的使用方法如下: //得到一个对应的enumerator对象 NSEnumerator * enumerator = [se ...
- ACM-ICPC 2018 徐州赛区网络预赛 B(dp || 博弈(未完成)
传送门 题面: In a world where ordinary people cannot reach, a boy named "Koutarou" and a girl n ...
- FileTest
package com.yd.wmsc.util; import java.io.File; public class FileTest { public static void main(Strin ...
- sql 更新 批量更新 更新得到主键
import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.jdbc.c ...
- DateAdapterForDay
public class DateAdapterForDay extends XmlAdapter<String, Date> { private SimpleDateFormat dat ...
- (转)linux命令总结之ip命令
linux命令总结之ip命令 原文:https://www.cnblogs.com/ginvip/p/6367803.html linux命令总结之ip命令 Linux的ip命令和ifconfig ...