众所周知当Android app代码足够多时, 编译会报方法数超过65535问题。 Google为了解决这个问题,提供了multi-dex方案, 即将代码编译成若干个dex文件,如classes.dex, classes2.dex...classes*.dex。
我们关心的是怎么解决这个问题,包括插件化、multi-dex等等方案, 网上相关的博客非常多。
但是有人想过Android为什么要报这个问题, 在哪里报的?
dalvik或art虚拟机在加载*.dex文件时会生成一个DexFile实例, 该实例包含dex文件的各种属性。
DexFile结构体的第一个属性pOptHeader指针的数据类型是DexHeader, 看看DexHeader的定义:
/*
* Direct-mapped "header_item" struct.
*/
struct DexHeader {
u1 magic[8]; /* includes version number */
u4 checksum; /* adler32 checksum */
u1 signature[kSHA1DigestLen]; /* SHA-1 hash */
u4 fileSize; /* length of entire file */
u4 headerSize; /* offset to start of next section */
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;
u4 methodIdsOff;
u4 classDefsSize;
u4 classDefsOff;
u4 dataSize;
u4 dataOff;
};
从DexHeader的定义可以看出, Android对方法总数和属性总数都限制为4个字节即65535。大概是下午犯困惯性思维写错了, 无符号整型4个字节取值范围为0~2^32-1。
方法、属性、类信息等等都有对象的offset, 为什么呢?
推断:
一个dalvik或者art虚拟机可以加载若干个dex文件, 在framework层限制了单个dex的最大方法数量、最大属性数量等等(详见DexFormat.java)。 感觉methodIdsSize数据类型应该是u2, 不明白为什么是u4。。。
offset是位置偏移,用于获取对应属性的头指针。 而每个属性对应连续的内存空间且单个实例(例如一个方法)占用空间大小相等, 虚拟机做个循环加载各个属性。 详见DexFile.cpp的dexFileSetupBasicPointer函数, 即每个属性分配头指针。
PS:我们写代码时一般是方法数量比属性数量多, 所有一般提示方法总数超过65535; 其实属性总量超过65535个时,也会报同样的问题!
从Native层DexFile结构体定义可以看出, 每个dex文件的方法数和属性数必须都小于65535, 否则会越界(因为只有4个字节)。
那么编译时的错误是哪里提示的呢? 经过搜索代码, 发现是在MemberIdsSection.java提示的。
DexFormat.MAX_MEMBER_IDX等于65535, 所以编译时会报错。
总结:
1、framework层限制了单个dex文件的最大方法数量、最大属性数量等等为65535(占2个字节),但native层DexHeader中对应字段定义为4个字节, 暂时没找出原因。。。
2、DexHeader各属性对应的offset是偏移, 用于获取真正的头指针。 例如dex文件含有10000个方法, methodIdsSize等于10000,通过methodIdsOff拿到头指针, 就可以读取这10000个方法各自的属性。
Android应用在方法数量超过65535时,会出现编译错误。这是因为Dalvik和ART虚拟机在加载.dex文件时,DexHeader结构对方法和属性总数限制为65535。Google为此提供了multi-dex解决方案。编译时的错误提示在MemberIdsSection.java,而DexHeader在native层中方法和属性数量字段为4个字节,可能的原因是预留扩展空间。此外,属性总数超过65535也会导致相同问题。
4126

被折叠的 条评论
为什么被折叠?



