隨著安卓平臺的不斷髮展與壯大,市場上大而全的應用比比皆是,產品需求的變更累積和UI互動的極致追求,除了 resources 檔案的俱增,在 Android Project 中依賴的 Library 和 自己寫的 Java 程式碼也會越來越多。這些變化,除了會導致打包出的 APK 檔案越來越大之外,當專案中java程式碼包含的方法數(method count)超出一個峰值時,編譯過程中就會出現如下錯誤:
較早版本的編譯系統中,錯誤內容如下:
Conversion to Dalvik format failed:
Unable to execute dex: method ID not in [0, 0xffff]: 65536複製程式碼
而在新版編譯系統中,是這樣:
trouble writing output:
Too many field references: 131000; max is 65536.
You may try using --multi-dex option.複製程式碼
儘管在不同版本的編譯系統中顯示的錯誤內容不盡相同,但內容中都提到了一個具體的數字:65536,這個數字也是本文要講到的核心內容:Android 64K Method Counts Limit 的峰值。詳細資訊,參考官網使用者指南:Configure Apps with Over 64K Methods。
Android 64K Method Counts Limit
Android Project 經過編譯打包,其中的Java程式碼(包括Library)轉化為DEX格式的位元組碼檔案,這是Android 5.0之前的 Dalvik 虛擬機器決定的(5.0之後改為 ART 虛擬機器),並且採用 short 型別引用 DEX 檔案中的 method,這也為method數量的峰值大小埋下了隱患。short 型別能夠表示的最大值是 65536,也就說單個 DEX 檔案中最多隻有 65536 個 method 能夠得到引用,如果程式碼執行了超出部分的 method 引用,自然會報錯,如 methodNotFound 等。1K 等於 1024,65536 剛好是 64K,為了便於稱呼和使用,就將這個限制規則統稱為 64K 方法數的引用限制。
為了解決 64K 方法數限制的問題,我們可以在專案中使用 multidex 配置,當專案中的方法數(包括:Android framework,library 和我們自己寫的程式碼)超過 64K 時,編譯系統會自動編譯出多個 DEX 檔案。
Multidex Support
Android 5.0 之前,安卓系統採用的是 Dalvik 虛擬機器,採用的是JIT技術(Just-in-time compilation,即時編譯,執行時編譯DEX位元組碼檔案,這也是以前為什麼安卓手機使用者總是詬病Android系統比iOS系統執行卡頓的原因),限制每個APK檔案只能包含一個 DEX 檔案(即 classes.dex
)。為了繞開這個限制,Google給我們提供了multidex support library 相容包,幫助我們實現應用程式載入多個DEX檔案,並且這個相容包作為程式的主DEX檔案,管理者其他DEX檔案的訪問。
注意:由於 Instant Run 機制利用的就是 multidex 原理,當專案中
minSdkVersion
引數設定為20或者更小,並且執行在 Android 4.4 (API 20) 或更低版本的裝置中時,Instant Run
將失效。
Android 5.0之後,安卓系統改用了ART虛擬機器(Android RunTime),採用的是OAT技術(Ahead-of-time,預編譯,在應用安裝的時候掃描應用中的所有DEX檔案,並編譯成一個.oat
格式的檔案供安卓裝置執行,所以相比Dalvik虛擬機器下的應用,安裝時間較長)。因此可以理解為,使用ART虛擬機器下的安卓系統自動支援APK檔案中多個DEX的載入。
注意:使用
Instant Run
時,如果專案中的minSdkVersion
引數設為21或更高版本,Android Studio編譯執行時會自動使應用支援multidex。但Instant Run
僅僅作用於debug版本,我們依然需要給release版本配置multidex來避開64K方法數的限制。
Config for Multidex With Gradle
Android Gradle 外掛在 Android SDK Build Tools 21.1 及更高版本的編譯工具上支援multidex作為編譯配置的一部分,所以確保我們的Android SDK Build Tools tools已經更新至21.1或更高版本,然後再來配置應用的multidex部分。
第一步,修改app/build.grale
檔案,使專案能夠使用multidex:
android {
compileSdkVersion 21
buildToolsVersion "21.1.0"
defaultConfig {
...
// Enabling multidex support.
multiDexEnabled true
}
...
}
dependencies {
compile 'com.android.support:multidex:1.0.0'
}複製程式碼
第二步,修改AndroidManifest.xml
檔案,引用MultiDexApplication
類:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yifeng.mdstudysamples">
<application
...
android:name="android.support.multidex.MultiDexApplication">
...
</application>
</manifest>複製程式碼
有時候,你可能還需要改變一下 javaMaxHeapSize 的大小:
android {
dexOptions {
javaMaxHeapSize "4g"
}
}複製程式碼
新增這些配置後,編譯工具會構建出一個主 DEX 檔案(classes.dex)和其他附屬 DEX 檔案(classes2.dex,classes3.dex 等,如果需要的話),編譯系統會將他們打包到 Apk 檔案中。
注意:一般我們會在專案中自定義一個繼承自
Application
的類,此時就需要重寫attachBaseContext()
方法,並在該方法裡面呼叫MultiDex.install(this)
來支援multidex,可參考:MultiDexApplication
Optimizing Multidex Development Builds
multidex 配置下的應用,編譯系統需要經過複雜的 DEX 分割運算,導致增加專案的編譯時間,從而影響開發人員的開發效率。我們可以使用 productFlavors 構建開發環境和正式環境的不同 flavors 來優化 multidex 的長時間編譯問題。
對於development flavor,設定 minSdkVersion
值為21,執行在Android 5.0以上版本的裝置中,使用 ART-supported 格式生成 multidex 的速度要快得多。對於 release flavor,minSdkVersion
值則設為應用實際支援的版本,編譯系統耗費較長的時間來生成適配多裝置的multidex APK檔案。如:
android {
productFlavors {
// Define separate dev and prod product flavors.
dev {
// dev utilizes minSDKVersion = 21 to allow the Android gradle plugin
// to pre-dex each module and produce an APK that can be tested on
// Android Lollipop without time consuming dex merging processes.
minSdkVersion 21
}
prod {
// The actual minSdkVersion for the application.
minSdkVersion 14
}
}
...
buildTypes {
release {
runProguard true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
}
dependencies {
compile 'com.android.support:multidex:1.0.0'
}複製程式碼
這樣,在開發階段,使用 devDebug 型別的變種 app,取消混淆,支援 multidex,並且執行在 5.0 及以上版本的裝置中,能夠加快編譯過程。有關 flavors 的資訊,以前寫過一篇文章:Android 利用Gradle實現app的環境分離,更多資訊可以參考英文手冊:Gradle Plugin User Guide,對應中文版譯文:Gradle Android外掛使用者指南翻譯。
strings count limit
前面我們說完單一 dex 檔案的方法數限制,事實上,還有一個字串數量限制。如果專案沒有使用 multidex 支援的話,當 strings 超出一定限制,編譯過程也會出錯:
Dex: Error converting bytecode to dex:
Cause: com.android.dex.DexIndexOverflowException: Cannot merge new index 65868 into a non-jumbo instruction!複製程式碼
不同專案編譯過程中報錯資訊裡的具體數字可能不同。Dex 檔案中出現的 string 預設是 4 個位元組即 16 位大小的 int 型別的數字引用使用的,即單個 Dex 檔案最多隻能引用 2^16 個 strings,當你的專案中出現超過這個最大數字的字串引用,而又沒有使用 multidex 支援,編譯過程便會出錯。
對於這種情況,除了使用 multidex,還有另外一種解決方案:jumboMode。這個模式允許單個 Dex 檔案支援到 32 為大小的 strings 引用,即 2^32 的引用峰值。使用方法是,在工程 app 模組下 build.gradle 檔案的 android 配置下新增:
dexOptions {
jumboMode true
}複製程式碼
注意:雖然單個 Dex 檔案中 strings 數量限制與 method 數量限制非常相似,但是如果專案方法數超過 64K, 我們還是需要使用 multidex 來解決,注意區分。有關這方面的更多詳細介紹,請參考 Dalvik bytecode。
Methods Count Statistics
儘管安卓系統支援multidex,我們還是要學會分析我們的應用,檢視各個部分的方法數,減少冗餘方法。這裡推薦幾個工具,幫助我們分析。
Library Methods count
一個線上統計 Android Library 方法數的網站,能夠統計出 Android 領域常見 libraries 的方法數、JAR 檔案和 DEX 檔案大小,並且能夠選擇不同版本,以圖表的形式展示出來。
該網站也提供了Android Studio的外掛,幫助我們分析專案中所依賴的libraries的方法數,如圖所示:
Apk Method Count
一個線上統計 APK 檔案方法數的開源專案,只需要將需要分析的APK檔案拖拽上傳至此,即可得到分析結果,如圖:
Android Studio APK Analyzer
最後,要重磅推薦Android Studio自帶的APK Analyzer,功能齊全,使用方便,絕對是安卓開發人員分析應用的不二選擇。使用 Android Studio APK Analyzer ,我們至少能夠做到:
檢視APK壓縮檔案中各個子檔案的大小(如DEX和resource檔案)
理解DEX檔案的結構
快速檢視APK檔案的版本資訊(直接檢視
AndroidManifest.xml
內容)直觀地比較兩個APK檔案內容
開發階段使用Android Studio開啟一個專案時,有三種方式使用APK Analyzer工具:
直接拖拽APK檔案到Android Studio的編輯視窗
雙擊開啟專案目錄
app/build/outputs/apk/
下的APK檔案點選選單欄
Build->Analyse APK...
並選擇APK檔案
關於我:亦楓,部落格地址:yifeng.studio/,新浪微博:IT亦楓
微信掃描二維碼,歡迎關注我的個人公眾號:安卓筆記俠
不僅分享我的原創技術文章,還有程式設計師的職場遐想