出现背景
1. 生成的apk在2.3以前的机器无法安装,提示INSTALL_FAILED_DEXOPT
2. 方法数量过多,编译时出错,提示: Conversion to Dalvik format failed:Unable to execute dex: method ID not in [0, 0xffff]: 65536
由于目前app支持的最低版本为4.0,所以主要解决的问题为方法数量过多
的问题。
问题原因
在Android系统中,一个App的所有代码都在一个Dex文件里面。Dex是一个类似Jar的存储了多有Java编译字节码的归档文件。因为Android系统使用Dalvik虚拟机,所以需要把使用Java Compiler编译之后的class文件转换成Dalvik能够执行的class文件。 当Android系统安装一个应用的时候,有一步是对Dex进行优化,这个过程有一个专门的工具来处理,叫DexOpt。DexOpt的执行过程是在第一次加载Dex文件的时候执行的。这个过程会生成一个ODEX文件,即Optimised Dex。执行ODex的效率会比直接执行Dex文件的效率要高很多。但是在早期Android系统中,DexOpt会把每一个类的方法id检索起来,存在一个链表结构里面。但是这个链表的长度是用一个short类型来保存的,导致了方法id的数目不能够超过65536个。当一个项目足够大的时候,显然这个方法数的上限是不够的。 因此出现的解决方案,就是把Dex文件分割成多个小的Dex文件,然后进行加载。
DexOpt优化内容
1.对于虚方法的调用,把方法索引修改成vtable索引。
2.把field的get/put修改成字节偏移量。把boolean/byte/char/short等类型的变量合并到一个32-bit的形式,更少的代码可以更有效地利用CPU的I-cache。
3.把一些大量使用的简单方法进行inline,比如String.length()。这样能减少方法调用的开销。
4.删除空方法。
5.加入一些计算好的数据。比如,VM需要一个hash table来查找类名字,我们就可以在Optimization阶段进行计算,不用放到DEX加载的时候了。
分包实例
后续可能遇到的问题
File dexDir = new File(e.dataDir, SECONDARY_FOLDER_NAME);
List files = MultiDexExtractor.load(context, e, dexDir, false);
if(checkValidZipFiles(files)) {
installSecondaryDexes(loader, dexDir, files);
} else {
Log.w("MultiDex", "Files were not valid zip files. Forcing a reload.");
files = MultiDexExtractor.load(context, e, dexDir, true);
if(!checkValidZipFiles(files)) {
throw new RuntimeException("Zip files were not valid.");
}
installSecondaryDexes(loader, dexDir, files);
}
由于当App体积不大,可以把自己编写的所有类都放入Main DEX中。待后续App功能增多,就需要决定,哪些类放入Main DEX优先加载,从而会出现ClassNotFoundException,影响App的正常运行。
美团等提供的划分方案:
把Service、Receiver、Provider涉及到的代码都放到Main DEX中,而把Activity涉及到的代码进行了一定的拆分,把首页Activity、Laucher Activity、欢迎页的Activity等所依赖的class放到了Main DEX中,把二级、三级页面的Activity以及业务频道的代码放到了Secondary DEX中。 MultiDex的install方法中,在加载完Main DEX后,会主动加载其余的dex,如class2.dex等。