原文出處[Google官網——配置方法數超過 64K 的應用](https://developer.android.com/tools/building/multidex.html#mdex-gradle)
### **背景**:
隨著 Android 平臺的持續成長,Android 應用的大小也在增加。當您的應用及其引用的庫達到特定大小時,您會遇到構建錯誤,指明您的應用已達到 Android 應用構建架構的極限。早期版本的構建系統按如下方式報告這一錯誤:
~~~
Conversion to Dalvik format failed:
Unable to execute dex: method ID not in [0, 0xffff]: 65536
~~~
較新版本的 Android 構建系統雖然顯示的錯誤不同,但指示的是同一問題:
~~~
trouble writing output:
Too many field references: 131000; max is 65536.
You may try using --multi-dex option
~~~
這些錯誤狀況都會顯示下面這個數字:65536。這個數字很重要,因為它**代表的是單個 Dalvik Executable (DEX) 字節碼文件內的代碼可調用的引用總數**。本頁介紹**如何通過啟用被稱為 Dalvik 可執行文件分包的應用配置來越過這一限制**,使您的應用能夠構建并讀取 Dalvik 可執行文件分包 DEX 文件。
#### **關于 64K 引用限制**
Android 應用 (APK) 文件包含 [Dalvik](https://source.android.com/devices/tech/dalvik/) Executable (DEX) 文件形式的可執行字節碼文件,其中包含用來運行您的應用的已編譯代碼。**Dalvik Executable 規范將可在單個 DEX 文件內可引用的方法總數限制在 65,536,其中包括 Android 框架方法、庫方法以及您自己代碼中的方法**。在計算機科學領域內,術語[千(簡稱 K)](https://en.wikipedia.org/wiki/Kilo-)表示 1024(或 2^10)。由于 65,536 等于 64 X 1024,因此這一限制也稱為“**64K 引用限制**”。
#### **Android 5.0 之前版本的 Dalvik 可執行文件分包支持**
Android 5.0(**API 級別 21**)之前的平臺版本使用 Dalvik 運行時來執行應用代碼。默認情況下,Dalvik 限制應用的每個 APK 只能使用單個 classes.dex 字節碼文件。要想繞過這一限制,您可以使用 [Dalvik 可執行文件分包支持庫](https://developer.android.com/tools/support-library/features.html#multidex),它會成為您的應用主要 DEX 文件的一部分,然后管理對其他 DEX 文件及其所包含代碼的訪問。
> **注**:如果您的項目配置時所面向的 Dalvik 可執行文件分包使用的是 minSdkVersion 20 或更低版本,并且您將其部署到運行 Android 4.4(API 級別 20)或更低版本的目標設備上,則 Android Studio 會停用 [Instant Run](https://developer.android.com/tools/building/building-studio.html#instant-run)。
關于 InstantRun可參考——[深度理解Android InstantRun原理以及源碼分析](https://github.com/nuptboyzhb/AndroidInstantRun)
#### **Android 5.0 及更高版本的 Dalvik 可執行文件分包支持**
Android 5.0(API 級別 21)及更高版本使用名為 ART 的運行時(Google在2014年,Google I/O大會,ART才正式取代Dalvik,提出了簡單方便的multidex的解決方案),后者**ART原生支持從 APK 文件加載多個 DEX 文件**。**ART 在應用安裝時執行預編譯,掃描 classesN.dex 文件,并將它們編譯成單個 .oat 文件,供 Android 設備執行。因此,如果您的 minSdkVersion 為 21 或更高值,則不需要 Dalvik 可執行文件分包支持庫。**
如需了解有關 Android 5.0 運行時的詳細信息,請參閱 [ART 和 Dalvik](https://source.android.com/devices/tech/dalvik/art.html)。
> 注:如果將應用的 minSdkVersion 設置為 21 或更高值,使用 Instant Run 時,Android Studio 會自動將應用配置為進行 Dalvik 可執行文件分包。由于 Instant Run 僅適用于調試版本的應用,您仍需配置發布構建進行 Dalvik 可執行文件分包,以規避 64K 限制。
#### **規避 64K 限制(盡量避免超過64K)**
在將您的應用配置為支持使用 64K 或更多方法引用之前,您**應該采取措施減少應用代碼調用的引用總數,包括由您的應用代碼或包含的庫定義的方法**。下列策略可幫助您避免達到 DEX 引用限制:
* **檢查您的應用的直接和傳遞依賴項** - 確保您在應用中使用任何龐大依賴庫所帶來的好處大于為應用添加大量代碼所帶來的弊端。一種常見的反面模式是,僅僅為了使用幾個實用方法就在應用中加入非常龐大的庫。**減少您的應用代碼依賴項往往能夠幫助您規避 dex 引用限制。**
* **通過 ProGuard 移除未使用的代碼** - 為您的版本構建[啟用代碼壓縮](https://developer.android.com/studio/build/shrink-code.html)以運行 ProGuard。**啟用壓縮可確保您交付的 APK 不含有未使用的代碼。**
使用這些技巧使您不必在應用中啟用 Dalvik 可執行文件分包,同時還會減小 APK 的總體大小。
#### **配置您的應用進行 Dalvik 可執行文件分包**
將您的應用項目設置為使用 Dalvik 可執行文件分包配置需要對您的應用項目進行以下修改,**具體取決于應用支持的最低 Android 版本。**
**如果您的 minSdkVersion 設置為 21 或更高值,您只需在模塊級 build.gradle 文件中將 multiDexEnabled 設置為 true**,如此處所示:
~~~
android {
defaultConfig {
...
minSdkVersion 21
targetSdkVersion 26
multiDexEnabled true
}
...
}
~~~
但是,如果您的 **minSdkVersion** 設置為 20 或更低值,則您必須按如下方式使用 [Dalvik 可執行文件分包支持庫](https://developer.android.com/tools/support-library/features.html#multidex):
* 修改模塊級 build.gradle 文件以啟用 Dalvik 可執行文件分包,并將 Dalvik 可執行文件分包庫添加為依賴項,如此處所示:
~~~
android {
defaultConfig {
...
minSdkVersion 15
targetSdkVersion 26
multiDexEnabled true
}
...
}
dependencies {
compile 'com.android.support:multidex:1.0.1'
}
~~~
* 根據是否要替換 [Application](https://developer.android.com/reference/android/app/Application.html) 類,執行以下操作之一:
* 如果您沒有替換 Application 類,請編輯清單文件,按如下方式設置 <application> 標記中的 android:name:
~~~
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp">
<application
android:name="android.support.multidex.MultiDexApplication" >
...
</application>
</manifest>
~~~
如果您替換了 Application 類,請按如下方式對其進行更改以擴展 [MultiDexApplication](https://developer.android.com/reference/android/support/multidex/MultiDexApplication.html)(如果可能):
~~~
public class MyApplication extends MultiDexApplication { ... }
~~~
或者,如果您替換了 Application 類,但無法更改基本類,則可以改為替換 [attachBaseContext()](https://developer.android.com/reference/android/content/ContextWrapper.html#attachBaseContext(android.content.Context)) 方法并調用[MultiDex.install(this)](https://developer.android.com/reference/android/support/multidex/MultiDex.html#install(android.content.Context)) 來啟用 Dalvik 可執行文件分包:
~~~
public class MyApplication extends SomeOtherApplication {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(context);
Multidex.install(this);
}
}
~~~
**構建應用后,Android 構建工具會根據需要構建主 DEX 文件 (classes.dex) 和輔助 DEX 文件(classes2.dex 和 classes3.dex 等)。然后,構建系統會將所有 DEX 文件打包到您的 APK 中**。
**運行時,Dalvik 可執行文件分包 API 使用特殊的類加載器來搜索適用于您的方法的所有 DEX 文件(而不是僅在主 classes.dex 文件中搜索)**。
#### **Dalvik 可執行文件分包支持庫的局限性**
Dalvik 可執行文件分包支持庫具有一些已知的局限性,將其納入您的應用構建配置之中時,您應該注意這些局限性并進行針對性的測試:
* 啟動期間在設備數據分區中安裝 DEX 文件的過程相當復雜,如果輔助 DEX 文件較大,可能會導致應用無響應 (ANR) 錯誤。在此情況下,您應該[通過 ProGuard 應用代碼壓縮](https://developer.android.com/studio/build/shrink-code.html)以盡量減小 DEX 文件的大小,并移除未使用的那部分代碼。
* 由于存在 Dalvik linearAlloc 錯誤(問題 [22586](http://b.android.com/22586)),使用 Dalvik 可執行文件分包的應用可能無法在運行的平臺版本早于 Android 4.0(API 級別 14)的設備上啟動。如果您的目標 API 級別低于 14,請務必針對這些版本的平臺進行測試,因為您的應用可能會在啟動時或加載特定類群時出現問題。代碼壓縮可以減少甚至有可能消除這些潛在問題。
* 由于存在 Dalvik linearAlloc 限制(問題 [78035](http://b.android.com/78035)),因此,如果使用 Dalvik 可執行文件分包配置的應用發出非常龐大的內存分配請求,則可能會在運行期間發生崩潰。盡管 Android 4.0(API 級別 14)提高了分配限制,但在 Android 5.0(API 級別 21)之前的 Android 版本上,應用仍有可能遭遇這一限制。
#### **聲明主 DEX 文件中需要的類**
為 Dalvik 可執行文件分包構建每個 DEX 文件時,構建工具會執行復雜的決策制定來確定主要 DEX 文件中需要的類,以便應用能夠成功啟動。如果啟動期間需要的任何類未在主 DEX 文件中提供,那么您的應用將崩潰并出現錯誤 java.lang.NoClassDefFoundError。
該情況不應出現在直接從應用代碼訪問的代碼上,因為構建工具能識別這些代碼路徑,但可能在代碼路徑可見性較低(如使用的庫具有復雜的依賴項)時出現。例如,如果代碼使用自檢機制或從原生代碼調用 Java 方法,那么這些類可能不會被識別為主 DEX 文件中的必需項。
因此,**如果您收到 java.lang.NoClassDefFoundError,則必須使用構建類型中的 [multiDexKeepFile](http://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.BuildType.html#com.android.build.gradle.internal.dsl.BuildType:multiDexKeepFile) 或 [multiDexKeepProguard](http://google.github.io/android-gradle-dsl/2.2/com.android.build.gradle.internal.dsl.BuildType.html#com.android.build.gradle.internal.dsl.BuildType:multiDexKeepProguard) 屬性聲明它們,以手動將這些其他類指定為主 DEX 文件中的必需項。如果類在 multiDexKeepFile 或 multiDexKeepProguard 文件中匹配,則該類會添加至主 DEX 文件**。
**multiDexKeepFile 屬性**
您在 multiDexKeepFile 中指定的文件應該每行包含一個類,并且采用 com/example/MyClass.class 的格式。例如,您可以創建一個名為 multidex-config.txt 的文件,如下所示:
~~~
com/example/MyClass.class
com/example/MyOtherClass.class
~~~
然后,您可以按以下方式針對構建類型聲明該文件:
~~~
android {
buildTypes {
release {
multiDexKeepFile file 'multidex-config.txt'
...
}
}
}
~~~
請記住,Gradle 會讀取相對于 build.gradle 文件的路徑,因此如果 multidex-config.txt 與 build.gradle 文件在同一目錄中,以上示例將有效。
**multiDexKeepProguard 屬性**
multiDexKeepProguard 文件使用與 Proguard 相同的格式,并且支持整個 Proguard 語法。如需了解有關 Proguard 格式和語法的詳細信息,請參閱 Proguard 手冊中的 [Keep Options ](http://proguard.sourceforge.net/manual/usage.html#keepoptions)一節。
您在 multiDexKeepProguard 中指定的文件應該在任何有效的 ProGuard 語法中包含 -keep 選項。例如,-keep com.example.MyClass.class。您可以創建一個名為 multidex-config.pro 的文件,如下所示:
~~~
-keep class com.example.MyClass
-keep class com.example.MyClassToo
~~~
如果您想要指定包中的所有類,文件將如下所示:
~~~
-keep class com.example.** { *; } // All classes in the com.example package
~~~
然后,您可以按以下方式針對構建類型聲明該文件:
~~~
android {
buildTypes {
release {
multiDexKeepProguard 'multidex-config.pro'
...
}
}
}
~~~
#### **優化開發構建中的 Dalvik 可執行文件分包**
Dalvik 可執行文件分包配置會大幅增加構建處理時間,因為構建系統必須就哪些類必須包括在主 DEX 文件中以及哪些類可以包括在輔助 DEX 文件中作出復雜的決策。這意味著使用 Dalvik 可執行文件分包的增量式構建通常耗時更長,可能會拖慢您的開發進度。
為了縮短耗時更長的 Dalvik 可執行文件分包輸出構建時間,請利用 [productFlavors](http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Product-flavors)(一個開發定制和一個發布定制,具有不同的 minSdkVersion 值)創建兩個構建變型。
* 對于開發定制,將 minSdkVersion 設置為 21。該設置將啟用一個名為 pre-dexing 的構建功能,此功能使用僅適用于 Android 5.0(API 級別 21)和更高版本的 ART 格式更快生成 Dalvik 可執行文件分包輸出。
* 對于發布定制,將 minSdkVersion 設置為適于您的實際最低支持級別。此設置生成的 Dalvik 可執行文件分包 APK 可兼容更多設備,但構建時間更長。
以下構建配置示例展示了如何在 Gradle 構建文件中設置這些定制:
~~~
android {
defaultConfig {
...
multiDexEnabled true
}
productFlavors {
dev {
// Enable pre-dexing to produce an APK that can be tested on
// Android 5.0+ without the time-consuming DEX build processes.
minSdkVersion 21
}
prod {
// The actual minSdkVersion for the production version.
minSdkVersion 14
}
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
}
dependencies {
compile 'com.android.support:multidex:1.0.1'
}
~~~
您完成此配置變更后,可以為增量式構建使用應用的 devDebug 變體,后者集 dev 產品定制與 debug 構建類型的屬性于一身。這將創建已啟用 Dalvik 可執行文件分包且禁用 proguard 的可調試應用(因為 minifyEnabled 默認為 false)。這些設置會使適用于 Gradle 的 Android 插件執行以下操作:
1. 執行 pre-dexing:將每個應用模塊和每個依賴項構建為單獨的 DEX 文件。
2. 將每個 DEX 文件加入 APK,并且不做任何修改(不執行代碼壓縮)。
3. 最重要的是,模塊 DEX 文件不執行合并操作,因此可以避免為確定主 DEX 文件的內容而進行長時間的計算。
**這些設置的好處是,可以進行快速的增量式構建,因為只有修改過的模塊的 DEX 文件才會在后續構建期間重新計算并重新打包。但是,這些構建的 APK 只能用于在 Android 5.0 設備上進行測試**。不過,由于是以定制形式實現配置,您保留了使用與發布相適的最低 API 級別和 ProGuard 代碼壓縮執行正常構建的能力。
您還可以構建其他變體,包括 prodDebug 變體構建,該變體雖然構建時間更長,但可用于開發以外的測試。在所示配置內,prodRelease 變體將是最終測試和發布版本。如需了解有關使用構建變體的詳細信息,請參閱[配置構建變體](https://developer.android.com/studio/build/build-variants.html)。
> 提示:由于您有適用于不同 Dalvik 可執行文件分包需求的不同構建變體,因此也可以為不同變體提供不同清單文件(這樣,只有適用于 API 級別 20 和更低版本的清單文件會更改 `<application>` 標記名稱),或者為每個變體創建不同的 Application 子類(這樣,只有適用于 API 級別 20 和更低版本的清單文件會擴展 [MultiDexApplication](https://developer.android.com/reference/android/support/multidex/MultiDexApplication.html) 類或調用 [MultiDex.install(this)](https://developer.android.com/reference/android/support/multidex/MultiDex.html#install(android.content.Context)))。
#### **測試 Dalvik 可執行文件分包應用**
編寫面向 Dalvik 可執行文件分包應用的儀器測試時,無需進行其他配置。[AndroidJUnitRunner](https://developer.android.com/reference/android/support/test/runner/AndroidJUnitRunner.html) 直接支持 Dalvik 可執行文件分包,前提是您使用 [MultiDexApplication](https://developer.android.com/reference/android/support/multidex/MultiDexApplication.html) 或替換您的自定義 [Application](https://developer.android.com/reference/android/app/Application.html) 對象中的 [attachBaseContext()](https://developer.android.com/reference/android/content/ContextWrapper.html#attachBaseContext(android.content.Context)) 方法,并調用 [MultiDex.install(this)](https://developer.android.com/reference/android/support/multidex/MultiDex.html#install(android.content.Context)) 以啟用 Dalvik 可執行文件分包。
或者,您可以替換 [AndroidJUnitRunner](https://developer.android.com/reference/android/support/test/runner/AndroidJUnitRunner.html) 中的 onCreate() 方法:
~~~
public void onCreate(Bundle arguments) {
MultiDex.install(getTargetContext());
super.onCreate(arguments);
...
}
~~~
> 注:目前不支持使用 Dalvik 可執行文件分包來創建測試 APK。
#### **遺留問題**:
1. 按照官網的使用multiDexKeepFile聲明主DEX 文件中需要的類,為啥按照官網的步驟,不可行呢?
2. 實際開發中可能會有更多的坑,而且官方權威的也不一定正確,一切從實際出發。
- 前言
- Android 熱補丁技術——資源的熱修復
- 插件化系列詳解
- Dex分包——MultiDex
- Google官網——配置方法數超過 64K 的應用
- IMOOC熱修復與插件化筆記
- 第1章 class文件與dex文件解析
- Class文件解析
- dex文件解析
- class與dex對比
- 第2章 虛擬機深入講解
- 第3章 ClassLoader原理講解
- 類的加載過程
- ClassLoade源碼分析
- Android中的動態加載
- 第4章 熱修復簡單講解
- 第5章 熱修復AndFix詳解
- 第6章 熱修復Tinker詳解及兩種方式接入
- 第7章 引入熱修復后代碼及版本管理
- 第8章 插件化原理深入講解
- 第9章 使用Small完成插件化
- 第10章 使用Atlas完成插件化
- 第11章 課程整體總結
- DN學院熱修復插件化筆錄
- 插件化
- 熱修復
- Android APP開發應掌握的底層知識
- 概述
- Binder
- AIDL
- AMS
- Activity的啟動和通信原理
- App啟動流程第2篇
- App內部的頁面跳轉
- Context家族史
- Service
- BroadcastReceiver
- ContentProvider
- PMS及App安裝過程