转载自:http://ingramchen.io/blog/2014/09/prevention-of-android-dex-64k-method-size-limit.html

08 September 2014

如果你有 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 數

好了,我們現在知道問題的成因,目標就很明確了:

  1. 開發進行期間,維持 method 數在 65536 以下 (未 proguard)
    開發時通常我們會用 Android 4.0 以上的手機來測,所以不用管 56000 method 數的限制。但要確保尚未做 proguard 之前,總 method 數要小於 65536。相信我,如果開發時每次 build 都要做 proguard 才能將 method 數壓在 65536 下,你會想死,每 build 一次都要幾分鐘以上啊。
  2. 正式發佈時,目標 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 數限制。建議:

  1. 慎選第三方 library,最好採用前先用 dex-method-count 度量一下
  2. scala, groovy 這類非原生的開發語言想都不要想,它們只會帶來更多的問題。用非 Java 語言開發可以,但是僅限於 NDK
  3. 要替 Google play service 瘦身
  4. square 出品的 library 都很適合 Android 使用
  5. Android 2.3 的可用 method 數更少,約 56000,不過這只要 proguard 後達成即可。

如果什麼都做了,但還是超過 64k 怎麼辦?網路上有很多將 dex 拆成多個的方法,並且動態讀進 .dex 檔。那些做法一個比一個髒,而且未來 ART 上也不能再用了。雖然如此,拆多個 dex 是唯一解決方法,真的碰到了不用也得用。又或者,山不轉路轉,向老闆提出這個 App 該拆成多個來賣了。


回響

ingramchen 2014-10-25 11:19 AM
小更新,最近 Android 5 出了之後, 64k limit 已經有官方解決法:
https://medium.com/@mustafa01ali/dexs-64k-limit-is-not-a-problem-anymore-well-almost-2b1faac3508
http://blog.osom.info/2014/10/multi-dex-to-rescue-from-infamous-65536.html
不過,現時這些解決法還是暫定的,未來幾個月應該會有更簡單更正式的解法。
ingramchen 2014-11-04 1:59 PM
Google release 官方的 multidex 解法
http://developer.android.com/tools/building/multidex.html
不過這個解法對 Android 2.3 LinearAlloc 限制無效,所以還是要注意。現階段的解法應該是放棄 2.3 的支援,或是對 2.3 build 另外一個功能較少的 apk,當然這步驟也是很麻煩…
ingramchen 2014-12-10 8:46 AM
Google play service 官方已經有解決的方法,見:
http://android-developers.blogspot.tw/2014/12/google-play-services-and-dex-method.html?m=1

[转]預防 Android Dex 64k Method Size Limit的更多相关文章

  1. Android学习笔记----解决“com.android.dex.DexIndexOverflowException: method ID not in [0, 0xffff]: 65536”问题

    同时在工程中引入了多个第三方jar包,导致调用的方法数超过了android设定的65536个(DEX 64K problem),进而导致dex无法生成,也就无法生成APK文件. 解决办法如下: 1.谷 ...

  2. 解决“com.android.dex.DexIndexOverflowException: method ID not in [0, 0xffff]: 65536”问题(l转)

    同时在工程中引入了多个第三方jar包,导致调用的方法数超过了android设定的65536个(DEX 64K problem),进而导致dex无法生成,也就无法生成APK文件. 解决办法如下: 1.谷 ...

  3. 解决android studio上“com.android.dex.DexIndexOverflowException: method ID not in [0, 0xffff]: 65935”问题

    我是在更换应用的一个jar包时发生的这个错误,网上查到说是因为同时在工程中引入了多个第三方jar包,导致调用的方法数超过了android设定的65935个(DEX 64K problem),进而导致d ...

  4. Android Dex文件格式解析

    Dex文件是Android虚拟机下的可执行文件,包含了应用程序所用到所有操作指令和运行时数据.在程序编译过程中,java源文件先被编译成class文件,然后通过dx工具将多个class文件整合为一个d ...

  5. Android Dex文件格式(二)

    第三块: 数据区         索引区中的最终数据偏移以及文件头中描述的map_off偏移都指向数据区, 还包括了即将要解析的class_def_item, 这个结构非常重要,下面就开始解析   c ...

  6. Android NDK开发method GetStringUTFChars’could not be resolved

    Android NDK开发method GetStringUTFChars'could not be resolved 图1 最近用到android的ndk,但在eclipse中提示method Ge ...

  7. Android Bug:Error:com.android.dex.DexException: Multiple dex files define Landroid/support/design/widget/CoordinatorLayout$LayoutParams;

    项目编译通过,运行时出现异常: Error:com.android.dex.DexException: Multiple dex files define Landroid/support/desig ...

  8. Error:Error converting bytecode to dex: Cause: com.android.dex.DexException: Multiple dex files define Lcom/lidroid/xutils/task/TaskHandler;

    Error:Error converting bytecode to dex: Cause: com.android.dex.DexException: Multiple dex files defi ...

  9. 解决Error:Android Dex: com.android.dex.DexIndexOverflowException: Cannot merge new index 65918 into a

    错误:Error:Android Dex: com.android.dex.DexIndexOverflowException: Cannot merge new index 65918 into a ...

随机推荐

  1. oracle根据sqlID查找相对应的sql语句

    转: 根据sqlID查找相对应的sql语句 2019-07-25 14:47:20 猛豪 阅读数 567更多 分类专栏: 数据库   版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议 ...

  2. 【Mybatis】MyBatis之插件开发(十)

    MyBatis插件开发原理 MyBatis采用责任链模式,通过动态代理组织多个插件(拦截器),通过这些插件可以改变MyBatis的默认行为(诸如SQL重写之类的),由于插件会深入到MyBatis的核心 ...

  3. Qt编写气体安全管理系统25-位置调整

    一.前言 位置调整功能,以前是直接写在设备按钮这个自定义控件类中,核心就是安装事件过滤器,识别鼠标按下.鼠标移动.鼠标松开,这三个event,做出相应的处理即可,后面发现这个功能其实很多自定义控件或者 ...

  4. Django之Restful API

    理解Restful架构:http://www.ruanyifeng.com/blog/2011/09/restful RESTful设计指南:http://www.ruanyifeng.com/blo ...

  5. (二十三)IDEA 构建一个springboot工程,以及可能遇到的问题

    一.下载安装intellij IEDA 需要破解 二.创建springboot工程 其他步骤省略,创建好的工程结构如下图: 三.配置springoboot工程 3.1 如上图src/main目录下只有 ...

  6. 斐波那契数列&&上台阶

    使用装饰器的场景 当我们想对多个函数增加一个相同的功能时,例如计数统计,缓存计算结果,记录日志等 # coding:utf-8 # [题目1] # 斐波那契数列 又称黄金分割数列,指的是这样的一个数列 ...

  7. 2019年Java面试题基础系列228道(5)

    21.存在两个类,B 继承 A,C 继承 B,我们能将 B 转换为C 么?如 C = (C) B: 这属于强制类型转换,如果被转换的B实例不是C类型,会有异常 比如你的ABC分别对应动物,猫,黑猫. ...

  8. caffe windows10 vs2015 cuda8.0 ->vs2013

    http://blog.csdn.net/xjz18298268521/article/details/52190184 http://www.cnblogs.com/xuanyuyt/p/57269 ...

  9. 查找searching

    查找searching 在有序数列中查找某一个数据时候的算法设计 查找表的分类 静态查找表:只进行查找操作 动态查找表:不断的插入不存在,删除已存在 查找表的操作 查找.插入.删除 查找也叫检索,是根 ...

  10. 第5课.linux进阶命令

    1.find:查找符合条件的文件 格式: find 目录名 选项 查找条件 eg: find /work/001_linux_basic/dira/ -name "test1.txt&quo ...