编译 Android 工程的时候,意外遇到了这样一个错误:
com.android.dex.DexIndexOverflowException: Cannot merge new index 66739 into a non-jumbo instruction!
通过谷歌搜索这个问题,发现 StackOverflow 上面已经有人遇到过相同的问题了:Application too big? Unable to execute dex: Cannot merge new index into a non-jumbo instruction。
大家给出的解释是,一个 dex 文件中至多容许存在 64K 个方法,超过这一限制,编译器就会报错。
至于为什么 Android 中会有这样一个诡异的数目限制,我猜测是因为 dalvik 中的某个计数器使用了 short
类型,因为 64K 刚好等于 65536。检索了一番 dalvik 的源码,发现确实如此,下面是埋下了这个大坑的代码:
// dalvik/dx/src/com/android/dx/merge/IndexMap.java
public IndexMap(Dex target, TableOfContents tableOfContents) {
...
public final short[] typeIds;
public final short[] protoIds;
public final short[] fieldIds;
public final short[] methodIds;
...
}
从代码中可以看出,不只是 methodIds
不能超过 65536 个,typeIds
、protoIds
以及 fieldIds
也都是如此。想必 dalvik 的开发者当初写下这几行代码的时候,心中是这么考虑的:六万五千多个方法还不够你们用的吗?
开发者在设计新功能或者新协议的时候,往往显得过于保守与短视。比如,IPv4 的设计者们显然并没有预料到我们今天会遭遇 IP 地址耗尽的危机。
现在,同样的问题出现在了 Android 开发者的面前。随着应用程序功能的日益复杂,源码的规模正在逐渐增大,势必有一天,代码中方法的数量终将超出限制。最彻底的解决办法自然是升级 dalvik 的源代码,把计数器的计数范围扩大。然而,由于当前版本的 dalvik 已经广泛运行在了大量的安卓设备中(全世界安卓设备的激活数已经超过了 10 亿台),这种毫无向前兼容性的解决办法显然不可取。
那么,作为开发者,该怎样避免这个问题呢?Android 的官方开发者网站上面有一篇专门讨论这个主题的文章: Building Apps with Over 65K Methods。
总结来说,有两种可选方案:
一种办法是减小程序的规模,这可以通过刨除无用代码以及减少项目的依赖来达成。不过这终究只是权宜之计,只能拖延时间,并不能阻止末日的到来。
另一种办法是把编译生成的字节码文件分割成为两个或者更多 dex 文件,以此规避单个 dex 文件中的方法总数不得超过 65536 的限制。
要知道,dalvik 强制限定了一个 APK 包中只能包含一个字节码文件。 这篇文章提供了一种有趣的思路,帮助我们理解为什么分割字节码文件是可行的。
网友评论
```
// dalvik/dx/src/com/android/dx/merge/InstructionTransformer.java
private static void jumboCheck(boolean isJumbo, int newIndex) {
if (!isJumbo && (newIndex > 0xffff)) {
throw new DexIndexOverflowException("Cannot merge new index " + newIndex +
" into a non-jumbo instruction!");
}
}
```
虚拟机在运行 `jumboCheck` 的时候,会判断 `nexIndex` 是否超过了 0xffff(也就是65535)。为什么做这个检查呢?因为要确保 `IndexMap` 里面的数组不会越界。