众所周知当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个方法各自的属性。