當前位置:
首頁 > 最新 > 如何解決 Android 應用方法數過多問題

如何解決 Android 應用方法數過多問題

這是一個什麼問題?

由於 Android 早期系統的一個問題,在我們開發應用中,每一個 dex 文件的方法數不能超過 65535,如果超過了這個數,APK 是沒辦法正常安裝載入的,具體造成這個問題的原因是這樣的:

當 Android 早期系統啟動 APP 時,為了提升執行效率,要對 dex 文件進行優化,即 DexOpt。DexOpt 過程是在第一次載入 dex 文件的時候執行的。在早期 Android 系統中,DexOpt 會把每一個類的方法 ID 檢索起來,存在一個鏈表結構裡面。但是這個鏈表的長度是用一個 short 類型來保存的,導致了方法 ID 的數目不能夠超過 65535 個。APP 體積足夠大的時候,那麼這個方法數的上限是不夠的。儘管在新版的 Android 系統中,DexOpt 修復了這個問題,但是我們仍然需要對老系統做兼容。

對於造成該問題的另外一個解釋:Dalvik Bytecode 的限制,因為 Dalvik 的 invoke-kind 指令集中,method reference index 只留了 16 bits,最多能引用 65535 個方法。具體參考鏈接:

http://stackoverflow.com/questions/21490382/does-the-android-art-runtime-have-the-same-method-limit-limitations-as-dalvik/21492160#21492160,

http://source.android.com/devices/tech/dalvik/dalvik-bytecode.html

Android 不同版本的差異

實際上 Android 這個問題在不同系統版本上又會有差異,在解決該問題時也需要兼顧考慮。

4.0 以下

不但要考慮方法數的問題,還需要考慮 LinearAlloc 的限制。主要是因為 DexOpt 使用 LinearAlloc 來存儲應用的方法信息,Dalvik LinearAlloc 是一個固定大小的緩衝區,Android 2.2 和 2.3 的緩衝區只有 5MB,Android 4.0 之後進行了提升。所以 4.0 以下系統,在方法數過多超出緩衝區大小時,即使沒有超出65535,也會造成 DexOpt 崩潰。即使正常編譯打包成 APK,在安裝的時候,也有可能會提示 INSTALL_FAILED_DEXOPT 而導致安裝失敗。

4.0 和 5.0

Android 使用 Dalvik 虛擬機,APP 安裝時候,守護進程 installd 調用 DexOpt,它對打包在 APK 裡面包含有 dex 文件位元組碼的 classes.dex 進行優化,優化得到的文件保存在 /data/dalvik-cache 目錄中,以 .odex 為後綴名,供運行時使用。但對於 5.0 以前系統本身來說它只處理一個 classes.dex。對於多個 dex 文件情況下,是我們需要考慮解決的問題。

5.0 以上

Android 系統使用 ART 虛擬機代替了之前的 Dalvik 虛擬機。ART 模式下,APK在安裝時候,守護進程 installd 調用另外一個工具 dex2oat,它對打包在 APK 裡面包含有 dex 文件位元組碼進翻譯 ,如果這時候發現多個 classes(..N).dex 文件,就會將他們最終合成為一個 .oat 的文件,並且也是保存在 /data/dalvik-cache 目錄中,供 APP 運行時使用,有效的解決了的這個問題。

可以選擇的解決方案

Google 原始方案

為解決 65535 問題,Google 提供了 Multidex Support 包,集成 Multidex 包之後,通過以下的方式來載入多個 dex 文件。

@Override

protected void attachBaseContext(Context base) {

super.attachBaseContext(context);

Multidex.install(this);

}

實際是上這種方案並沒有徹底解決問題,主要的問題是:

載入 dex 文件時間長

由於在主線程載入 dex2 文件,造成 ANR,Crash

子進程非同步載入子 dex 文件

這個方案實際上是對 Google 提供的 Multidex 方案做一些改進。

Multidex.install() 方法主要做了三件事情:

從 APK 中加壓出 dex 文件

完成 DexOpt 操作

把優化後的 odex 文件目錄添加到 ClassLoader 中

其中主要耗時的工作是前兩項,所以基於這樣的情況做下面的改進:

APP 啟動時主進程掛起等待

在子進程中進行 Multidex.install(this) 的工作,它從 APK 中解壓 dex2 文件,然後完成 DexOpt 的工作

同時在子進程中啟動歡迎界面,子進程任務完成後退出

主進程監測到子進程的解壓和 DexOpt 工作完成後,再從掛起狀態恢復,繼續工作

此時在主進程中再調用一次 Multidex.install(this) 作用是將優化後的優化後的 odex 文件目錄添加到 ClassLoader 中(由於解壓出 dex2 文件,dexOpt工作在子進程中已經完成,Multidex會跳過這兩項工作)

主進程啟動正常的工作流程

優點:

Multidex 的處理和業務邏輯耦合性很低

主 dex 文件中必須的 class 文件比較少,便於後期維護,避免手動拆分 dex 文件帶來的各種問題

通用性比較強,便於機型試配,測試等

缺點:

子 dex 文件處理需要在子進程中,此時需要等待時間。可以通過控制welcome,splash頁面來解決

子 dex 文件大時候,啟動時間會比較長

插件化+動態載入

社區有一些比較成熟的方案,比如攜程的方案,阿里的 Atlas 方案,早期 360Droid Plugin 等,思路都是大同小異:

對某些子模塊進行動態載入,結合插件化的實現

重寫 AAPT 對資源進程處理

對系統 API 進行 Hook,以便於啟動相應的子模塊的 Activity 等

通過代理 Activity Delegate 等方式解決系統組件需要註冊的問題

優點:

動態載入不會對產品體驗造成影響,甚至是用戶無感知的載入

基於插件化,組件化的基礎上來實現更好

缺點:

機型覆蓋,適配等問題。需要全方位的測試

對於Android的各個版本需要追蹤,維護成本比較高

延遲載入

Instagram 有一個開源項目 lazy-module-loader,參考他們的方案我們也可以自己分 dex 文件,然後通過 ClassLoader 來載入,原理是這樣:

對一些獨立的 jar 或 dex 文件載入進程延遲載入

通過代理的方式來解決 Activity, Service 的啟動問題

也可以做到用戶無感知

優點:

沒有對系統 Framework 層進行 Hook,所以通用性比較強

後期維護成本比較低

缺點:

不支持資源的載入

手動拆分 dex 文件,成本也會比較高

雪球的選擇

根據上邊的一些方案分析,我們認為雪球 APP 最適合的方案是非同步載入子 dex 文件來解決這個問題,主要考慮了以下幾個因素:

方法數考慮:雪球 APP 目前來說算一個中等量級的應用,方法數大概在65535-100000 之前這樣的級別

版本支持:雪球目前最低支持的 Android 版本是 4.0,所以不需要考慮 4.0 以下版本的 LinearAlloc 的限制。在 5.0 以上版本由於我們方法數不是太大,使用 Android 系統的自動的 Multidex 處理方式,也不會造成太大卡頓,所以我們主要考慮處理 4.0-5.0 之間的用戶

用戶佔比:雪球 APP 4.0-5.0 版本之間的用戶的佔比大約在 5% 左右,而且佔比一直下降中。所以對於主業務邏輯不需要做大規模的改動,又能兼顧這5% 左右的用戶,還是一個比較適合的方案

寫在最後

Android 的這個方法數問題雖然說不是一個很難解決的問題,但是解決這個問題的思路是雪球的工程師團隊一直以來的習慣:儘可能的多了解問題產生的原因,然後找到已經存在的解決方案了解他們的原理,最後根據我們的應用的特點有針對性的去選擇和改進。換成其他的技術問題按照這樣的思路也會得到最佳的結果。

還有一件事


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 雪球工程師團隊 的精彩文章:

TAG:雪球工程師團隊 |