multidex
这是一篇关于multidex的使用、源码解析、存在哪些坑的文章。
使用
网上一大坨,且使用比较简单,所以忽略。
源码解析
- MultiDex
- MultiDexApplication
- MultiDexExtractor
- ZipUtil
1、首先检查手机的java vm已经支持了multidex,只要java vm的版本大于等于2.1即是原生支持了miltidex的功能,multidex的support包就是无效的了。
private static final boolean IS_VM_MULTIDEX_CAPABLE =
isVMMultidexCapable(System.getProperty("java.vm.version"));
2、将 secondary dex从apk中提取出来
- sourceApk:/data/app/pkgname.apk
- dexDir:/data/data/pkgname/code_cache/secondary-dexes
- 进行文件CRC的检查,以及是否需要强制更新
实际的提取是在performExtractions中的extract()
方法中:
extract(apk, dexFile, extractedFile, extractedFilePrefix);
apk(ZipFile) 指向/data/app/pkgname.apk
dexFile(ZipEntry) 指向classes2.dex
extractTo(File) 指向/data/data/pkgname/code_cache/secondary-dexes/pkgname.apk.classes2.zip
extractedFilePrefix(String) pkgname.apk.classes
- 根据apk文件生成apk文件的对应的ZipFile
- 从生成的ZipFile中提取出classes2.dex
- 生成最后的zip文件的路径
- 将dexFile写入到extractTo指向的文件路径上
- 完整dex的提取
3、安装secondary dex
提供三种不同sdk版本的安装V4、V14、V19。
其实就是通过反射
的方式去修改classLoad的值
延伸阅读:dalvik.system.BaseDexClassLoader
见classload章节
是什么引起Dex超出
Dex的结构定义?
/*
* Direct-mapped "header_item" struct.
*/
struct DexHeader {
u1 magic[8];
u4 checksum;
u1 signature[kSHA1DigestLen];
u4 fileSize;
u4 headerSize;
u4 endianTag;
u4 linkSize;
u4 linkOff;
u4 mapOff;
u4 stringIdsSize;
u4 stringIdsOff;
u4 typeIdsSize;
u4 typeIdsOff;
u4 protoIdsSize;
u4 protoIdsOff;
u4 fieldIdsSize;
u4 fieldIdsOff;
u4 methodIdsSize; // 这里存放了方法字段索引的大小,methodIdsSize的类型为u4
u4 methodIdsOff;
u4 classDefsSize;
u4 classDefsOff;
u4 dataSize;
u4 dataOff;
};
/*
* These match the definitions in the VM specification.
*/
typedef uint8_t u1;
typedef uint16_t u2;
typedef uint32_t u4;
typedef uint64_t u8;
typedef int8_t s1;
typedef int16_t s2;
typedef int32_t s4;
typedef int64_t s8;
methodIdsSize的类型是uint32_t,它是4字节大小的,为2^32 = 65536 * 65536,比65536大的多。
所以不是因为dex结构的定义导致方法书超出的。
DexOpt优化造成?
当Android系统安装一个应用的时候,有一步是对Dex进行优化,这个过程有一个专门的工具来处理,叫DexOpt。DexOpt的执行过程是在第一次加载Dex文件的时候执行的。这个过程会生成一个ODEX文件,即Optimised Dex。执行ODex的效率会比直接执行Dex文件的效率要高很多。
DexOpt会产生两个问题:
- DexOpt 会把每一个类的方法 id 检索起来,存在一个链表结构里面,但是这个链表的长度是用一个 short 类型来保存的,导致了方法 id 的数目不能够超过65536个。当一个项目足够大的时候,显然这个方法数的上限是不够的。
- Dexopt 使用 LinearAlloc 来存储应用的方法信息。Dalvik LinearAlloc 是一个固定大小的缓冲区。在Android 版本的历史上,LinearAlloc 分别经历了4M/5M/8M/16M限制。Android 2.2和2.3的缓冲区只有5MB,Android 4.x提高到了8MB 或16MB。当方法数量过多导致超出缓冲区大小时,也会造成dexopt崩溃。
新版本的Android系统已经优化DexOpt方法数65K的限制问题,并且扩大了LinearAlloc限制,但是我们仍然需要对低版本的Android系统做兼容。
可问题是DexOpt是在安转时,并不是在构建的时候,所以根本的原因还不在这里。所以它的限制是在安装时。
DexMerger的检测?
将第三方的jar的代码和自己的代码合并成dex。
DexMerger.java
...
private void mergeMethodIds() {
new IdMerger<MethodId>(idsDefsOut) {
@Override TableOfContents.Section getSection(TableOfContents tableOfContents) {
return tableOfContents.methodIds;
}
@Override MethodId read(Dex.Section in, IndexMap indexMap, int index) {
return indexMap.adjust(in.readMethodId());
}
@Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) {
if (newIndex < 0 || newIndex > 0xffff) {
throw new DexIndexOverflowException(
"method ID not in [0, 0xffff]: " + newIndex);
}
indexMap.methodIds[oldIndex] = (short) newIndex;
}
@Override void write(MethodId methodId) {
methodId.writeTo(idsDefsOut);
}
}.mergeSorted();
}
...
可以看出在这里强制做了大小的判断。
为什么要判断?
https://source.android.com/devices/tech/dalvik/dalvik-bytecode.html
这是dalvik bytecode的限制。65536是bytecode的16位限制算出来的:2^16。
如何进行分dex
参数配置:
--multi-dex:多 dex 打包的开关。
--main-dex-list=<file>:参数是一个类列表的文件,在该文件中的类会被打包在第一个 dex 中。
--minimal-main-dex:只有在--main-dex-list 文件中指定的类被打包在第一个 dex,其余的都在第二个 dex 文件中。
如何我们想对multiDex有更多的控制的话,可以在gradle中加入:
afterEvaluate {
tasks.matching {
it.name.startsWith('dex')
}.each { dx ->
if (dx.additionalParameters == null) {
dx.additionalParameters = []
}
dx.additionalParameters += '--multi-dex'
// 设置multidex.keep文件中class为第一个dex文件中包含的class,如果没有下一项设置此项无作用
dx.additionalParameters += "--main-dex-list=keeppath".toString()
//此项添加后第一个classes.dex文件只能包含-main-dex-list列表中class
dx.additionalParameters += '--minimal-main-dex'
// 设置主dex大小
dx.additionalParameters += '--set-max-idx-number=20000'
}
}