一个诡异的 Android 大坑

Java基础

浏览数:13

2019-8-22

编译 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 个,typeIdsprotoIds 以及 fieldIds 也都是如此。想必 dalvik 的开发者当初写下这几行代码的时候,心中是这么考虑的:六万五千多个方法还不够你们用的吗?

开发者在设计新功能或者新协议的时候,往往显得过于保守与短视。比如,IPv4 的设计者们显然并没有预料到我们今天会遭遇 IP 地址耗尽的危机。

现在,同样的问题出现在了 Android 开发者的面前。随着应用程序功能的日益复杂,源码的规模正在逐渐增大,势必有一天,代码中方法的数量终将超出限制。最彻底的解决办法自然是升级 dalvik 的源代码,把计数器的计数范围扩大。然而,由于当前版本的 dalvik 已经广泛运行在了大量的安卓设备中(全世界安卓设备的激活数已经超过了 10 亿台),这种毫无向前兼容性的解决办法显然不可取。

那么,作为开发者,该怎样避免这个问题呢?Android 的官方开发者网站上面有一篇专门讨论这个主题的文章: Building Apps with Over 65K Methods

总结来说,有两种可选方案:

一种办法是减小程序的规模,这可以通过刨除无用代码以及减少项目的依赖来达成。不过这终究只是权宜之计,只能拖延时间,并不能阻止末日的到来。

另一种办法是把编译生成的字节码文件分割成为两个或者更多 dex 文件,以此规避单个 dex 文件中的方法总数不得超过 65536 的限制。

要知道,dalvik 强制限定了一个 APK 包中只能包含一个字节码文件。 这篇文章提供了一种有趣的思路,帮助我们理解为什么分割字节码文件是可行的。

作者:song4