预防 Android Dex 64k Method Size Limit

1218人阅读 评论(0) 收藏 举报
分类:
感谢原文作者ingramchen分享

如果你有 Android App 持续开发一年以上,那你多半已经遇过很有名的 Dex 64k method 数量上限:

Unable to execute dex: method ID not in [0, 0xffff]: 65536
Conversion to Dalvik format failed: Unable to execute dex: method ID not in [0, 0xffff]: 65536

以及 LinearAlloc exceeded 5MB capacity:

ERROR/dalvikvm(4620): LinearAlloc exceeded capacity (5242880), last=...

两者的限制不同,但成因却很类似,都是因为 App 的程式太大。如果你 很幸运的 遇到这个问题 (是幸运没错,App 要受欢迎才有机会变大) 那你八成已经靠 stackoverflow 上的各种奇怪的药方暂时的解决这个问题。

App 太大无法安装这问题现在无解,即使到的新的 runtime ART 也是一样。我们 Android 开发者会跟这个问题相处一段很长的时间,所以能做的就是了解它,并且事先做好预防。
Dex 64k method size limit

.dex 档是 Dalvik EXecutable,裡面存的是 dex byte code,可在 Davlik VM 上执行。你 unzip 解开 .apk 后就会看到一个 classes.dex 档,就是它了。不过,dex method 64k 上限数跟 dex 档案的格式无关。根据 stackoverlfow 回应的说法,是因为 Dalvik 指令集裡,执行 method 的 invoke-kind index 大小只给了 16bit,所以一个 Android 程式裡最多只能执行前 65536 个 method,后面多的都不能用。

因为是指令集的限制,所以新一代 ART Runtime 也受同样的限制。.dex 档头裡已经有写总 method 数,你可以用 Android SDK 内附的 dexdump 指令查看你的 app 定义了多少个 method:

cd android-sdk-macosx
./build-tools/19.1.0/dexdump -f  /path/to/your/apk | grep method_ids_size

下面是范例输出,这个范例 apk 定义了 51306 个 method

method_ids_size     : 51306

5万个 method 很多啊,快到64k上限了,一个 App 真能写这麽多 method 吗?其实不然,后面会解释这一大票 method 大多是第三方的 .jar 造成的。
LinearAlloc 5MB capacity

有关 LinearAlloc 的问题,网路上已经有很好的解说。简单说就是 Android 程式执行前会将 class 读进 LinearAlloc 这块 buffer 裡,它的大小在 Android 2.3 之前是 5MB,到了 4.0 后才改成 8MB 或 16MB。5MB 太小了,通常你还没踩到 64k method 限制时,就会先踩到 LinearAlloc 的问题。

这个问题到了 4.0 才改善,但是 2.3 还有约十几 % 的市场,所以我们还是得面对它。注意 5MB buffer 的限制不是 classes.dex 的档案大小的限制,像我们自家的 App classes.dex 的大小已经 7 MB 了,还是可以在 2.3 执行。最主要还是看 class 结构的複杂性,以及总 method 数。

    根据我们的经验,总 method 数要维持在 56000 以下才能塞进 5MB buffer 裡。
    根据 这个 issue 22586,太複杂的 interface 继承会出问题,像是用 scala 语言开发 Android 就容易出错。
    某些 Andoird 2.3 的 LinearAlloc 的可用大小比其他 2.3 的还小,我们的用户中使用 HTC Desire 的手机特别容易遇到这个错误

错误讯息 INSTALL_FAILED_DEXOPT

安装 apk 时,如果出现上面提到的两种错误,你通常会看到错误讯息有 INSTALL_FAILED_DEXOPT 这行。dexopt 是 dex optimization 的意思,这一步骤会发生在安装完 apk 之后,它会检验 .dex 裡面的指令集是不是合法,也会验 method 的上限数。超过上限的话,app 还没启动就被这一步挡下,直接喷错。dexopt 也会试著将所有 class/method 都读进 VM 验证,这自然会运用到 LinearAlloc buffer。如果 buffer 不够也是直接喷了。所以程式太大的话,通通会死在 dexopt 这过程裡。
目标 method 数

好了,我们现在知道问题的成因,目标就很明确了:

    开发进行期间,维持 method 数在 65536 以下 (未 proguard)
    开发时通常我们会用 Android 4.0 以上的手机来测,所以不用管 56000 method 数的限制。但要确保尚未做 proguard 之前,总 method 数要小于 65536。相信我,如果开发时每次 build 都要做 proguard 才能将 method 数压在 65536 下,你会想死,每 build 一次都要几分钟以上啊。
    正式发佈时,目标 method 数 56000 以下 (proguard 后)
    正式发佈 apk 时,proguard 这步骤通常会做。所以确保包给 Android 2.3 的版本经过 proguard 过后,method 数可以压在 56000 以下即可。

注意 56000 这数字只是我们的经验值,实际的情形可能有出入。
大量的 method 数哪来的

你的 App 也许才几千个 method,不过你跑一下上面的 dexdump 指令,你可能会发现你已经用掉二、三万的 method 了。有关这个问题,今年六月的时候有高手 @rotxed 详细解说,请大家务必去读一遍。本文只是按照那篇的建议,做一些延伸性的探讨。

为了测试,我们用 Android Studio 开启一个 target Android 2.3 空白专案,就选 Google Play Services Activity 吧。然后跑一下由 mihaip 开发的 dex-method-count 这个程式来计数。它可以列出 apk 中所有 package 下 method 的总数,来看看这个空白程式的结果吧:

Read in 30788 method IDs.
<root>: 30788
    android: 8923
        support: 6825
            v4: 4209
            v7: 2616
    java: 723
    javax: 5
    org: 75
        apache: 24
            http: 24
        json: 39
        xmlpull: 12
            v1: 12
    dalvik: 2
        system: 2
    com: 21057
        google: 21029
            ads: 124
            android: 20905
                gms: 20905



这个空白程式已经用掉 3 万个 method 了,最大宗的是 com.google.android.gms,也就是 google play service (5.x 版),它直接吃掉 20905 个 method,这也是上面提到 @rotxed 文中的主要内容。不过值得注意的事,内建的 android.* 与 java.* 这两个 package 大概只佔 2000 个 method,但是 support library v4 和 v7 则吃掉 7000 个。如果你的 App 要 target Android 2.3 版,support v4 是支援 Fragment ,v7 则是支援 ActionBar,这两个 library 都很难避免的。

    内建 class 加上 support library v4, v7,你的 method 可用数直接少一万

知名 library method 数

好了,现在我们心裡有数了,来看看知名的 Android library 的 method 数吧
library     method count     function
joda-time     4602     date time
com.fasterxml.jackson     8346     JSON
google-gson     881     JSON
com.squareup.okhttp     1301     http/spdy
com.squareup.picasso     445     image/network
volley     376     image/network
guava     13587     mighty tools
commons-io     1196     I/O tools
commons lang3     2415     general tools
dagger     268     Dependency Injection
protobuf     5310     protobuf
protobuf-lite     800     protobuf
square wire + okio     384 + 381     protobuf
com.amazonaws.services.s3     11798     AmazonAWS SDK for SD (2.0)
dropbox + misc libs     412 + 2864     Dropbox SDK
bouncycastle     8875     Crypto (required by Dropbox)



上面列的有点杂,包含网路类、图形类、工具类、加密类、常见的 S3 和 Dropbox 等等,不过你可以发现有些工具硬是比其他同质性的大的多。

    Json 的工具:我自己偏好使用 jackson,但看到它的 method 数要 8346,整个都傻了,我想 Android 还是比较适合用小很多的 google-gson。

    一般工具类:guava 这工具是很好用的,但是 method 数达到破表的 13587,而且由于它内部 class 交互依赖,所以 proguard 对它无效。因此 guava 算是直接出局了,不管有什麽理由你都不该在 Android 使用 guava。你必须用 commons-io, commons-lang3 之类的工具取代。

    取代 java Date 的工具:joda-time,4602 个 method,我觉得太多了,除非有大量的日期处理,比方说你写的是日曆类的 App,才可以考虑它。不然还是建议用 commons langs3 与 android.text.format.DateUtils 就好了。

    picasso 和 volley 都是不错的图片下载与显示的工具,小而且好用。我们是选 volley,因为它兼具 image loader 与 http client 的功能。

    protobuf 一般 app 应该比较少用,不过我们的 App Cubie 用很凶,如果你有需要 binary protocol ,一般也是推荐用 protobuf。原生的 protobuf library 吃掉 5000 个 method,很可怕,记得在 Android 要换成 lite 版的版本。但是原生 protobuf 产生的程式码随便就会超过 1000 个 method。所以建议在 Android 上整套换掉,改用 square 的 wire,它的 method 数少很多。

    amazonaws S3 Android SDK method 数达 11798,吓呆了,我只是要上传个档案到 S3 就要吃掉一万个 method ? 对付这个 SDK,proguard 是有用的。但是开发的阶段你可不想每次都跑 proguard 啊!怎麽办?只能 手动 proguard -- 把它的 source code 拿出来,手动删掉不要的 method,再包成新的 jar

    Dropbox SDK 应该不少 App 都有机会用到,但是它依赖的 jar 很多,而且还加进 bouncycastle 这个 8875 method 的加密 library,总 method 数直接衝到 12151 个,我们有多少个一万可以挥霍?

预防 method 数爆增

综观上面的统计,有个趋势是,如果 library 本来是设计给 server-side Java 用的,它的 method 数动辄上千,破万的也有。除非有必要,你不该在 Android 使用这些工具,你必须找专门设计给 Android 的 library。现在 Android 已经进入成熟期,有很多专门的工具了,花点心思就能找到。这裡特别推荐 square 这家公司出品的工具,很多都是小而质精,建议大家找工具时可以先逛逛他们的 github。

    使用专门设计给 Android 的 library,不要找 server-side 的 Java library

我们前面提到一个空白的专案,撇开 google play service 不提,就要一万个 method。而 google play service 则要价两万,不过靠一些工具可以减少到一万。所以算起来还有四万的额度,视情况你最少必须保留一万个 method 给自己的 App。这样就剩三万,看起来很多,但看看上面的表,不小心加个 SDK 就花掉五千、一万个 method,一下就没了。

所以 library 要慎选啊。你要把额度留给你无法挑选的 library,像是 google play service 这种无法取代的。最后,给各位个参考,我们的 App Cubie,历经三年的开发,不含任何 library 的 method 数现在达两万了。你可以想像我们现在到处在查程式中可以减 method 数的地方,快疯了!
总结

由于 Android dex 的种种限制,造成 method 数量有上限的问题,再加上第三方 library 挥霍无度,使得即使你的 App 还很小,却不小心会撞上这个 64k method 数限制。建议:

    慎选第三方 library,最好採用前先用 dex-method-count 度量一下
    scala, groovy 这类非原生的开发语言想都不要想,它们只会带来更多的问题。用非 Java 语言开发可以,但是仅限于 NDK
    要替 Google play service 瘦身
    square 出品的 library 都很适合 Android 使用
    Android 2.3 的可用 method 数更少,约 56000,不过这只要 proguard 后达成即可。

如果什麽都做了,但还是超过 64k 怎麽办?网路上有很多将 dex 拆成多个的方法,并且动态读进 .dex 档。那些做法一个比一个葬,而且未来 ART 上也不能再用了。虽然如此,拆多个 dex 是唯一解决方法,真的碰到了不用也得用。
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:218681次
    • 积分:3960
    • 等级:
    • 排名:第8262名
    • 原创:174篇
    • 转载:17篇
    • 译文:2篇
    • 评论:97条
    博客专栏
    文章分类
    最新评论