隨着安卓平臺的不斷髮展與壯大,市場上大而全的應用比比皆是,產品需求的變動累積和UI交互的極致追求,除了 resources 文件的俱增,在 Android Project 中依賴的 Library 和 本身寫的 Java 代碼也會愈來愈多。這些變化,除了會致使打包出的 APK 文件愈來愈大以外,當項目中java代碼包含的方法數(method count)超出一個峯值時,編譯過程當中就會出現以下錯誤:javascript
較早版本的編譯系統中,錯誤內容以下:php
Conversion to Dalvik format failed:
Unable to execute dex: method ID not in [0, 0xffff]: 65536複製代碼
而在新版編譯系統中,是這樣:html
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。java
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 方法數的引用限制。android
爲了解決 64K 方法數限制的問題,咱們能夠在項目中使用 multidex 配置,當項目中的方法數(包括:Android framework,library 和咱們本身寫的代碼)超過 64K 時,編譯系統會自動編譯出多個 DEX 文件。git
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
將失效。github
Android 5.0以後,安卓系統改用了ART虛擬機(Android RunTime),採用的是OAT技術(Ahead-of-time,預編譯,在應用安裝的時候掃描應用中的全部DEX文件,並編譯成一個.oat
格式的文件供安卓設備執行,因此相比Dalvik虛擬機下的應用,安裝時間較長)。所以能夠理解爲,使用ART虛擬機下的安卓系統自動支持APK文件中多個DEX的加載。android-studio
注意:使用
Instant Run
時,若是項目中的minSdkVersion
參數設爲21或更高版本,Android Studio編譯運行時會自動使應用支持multidex。但Instant Run
僅僅做用於debug版本,咱們依然須要給release版本配置multidex來避開64K方法數的限制。微信
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
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插件用戶指南翻譯。
前面咱們說完單一 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。
儘管安卓系統支持multidex,咱們仍是要學會分析咱們的應用,查看各個部分的方法數,減小冗餘方法。這裏推薦幾個工具,幫助咱們分析。
一個在線統計 Android Library 方法數的網站,可以統計出 Android 領域常見 libraries 的方法數、JAR 文件和 DEX 文件大小,而且可以選擇不一樣版本,以圖表的形式展現出來。
該網站也提供了Android Studio的插件,幫助咱們分析項目中所依賴的libraries的方法數,如圖所示:
一個在線統計 APK 文件方法數的開源項目,只須要將須要分析的APK文件拖拽上傳至此,便可獲得分析結果,如圖:
最後,要重磅推薦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亦楓
微信掃描二維碼,歡迎關注個人我的公衆號:安卓筆記俠
不只分享個人原創技術文章,還有程序員的職場遐想