為方法數超過 64K 的應用啟用多 dex 文件
當您的應用及其引用的庫超過 65536 個方法時,您會遇到一個編譯錯誤,指明您的應用已達到 Android 編譯架構規定的引用限制:
trouble writing output:
Too many field references: 131000; max is 65536.
You may try using --multi-dex option.
較低版本的編譯系統會報告一個不同的錯誤,但指示的是同一個問題:
Conversion to Dalvik format failed:
Unable to execute dex: method ID not in [0, 0xffff]: 65536
這種錯誤情況都會顯示一個共同的數字:65536。此數字表示單個 Dalvik Executable(DEX)字節碼文件內的代碼可調用的引用總數。該文章主要介紹如何通過啟用稱為“多 DEX 文件”的應用配置(該配置使您的應用能夠編譯和讀取多個 DEX 文件)來越過這一限制。
關于 64K 引用限制
Android 應用(APK)文件包含 Dalvik Executable(DEX)文件形式的可執行字節碼文件,這些文件包含用來運行您的應用的已編譯代碼。Dalvik Executable 規范將可在單個 DEX 文件內引用的方法總數限制為 65536,其中包括 Android 框架方法、庫方法以及您自己代碼中的方法。由于 65536 等于 64 * 1024,因此這一限制成為“64K 引用限制”。
Android 5.0 之前版本的多 dex 文件支持
Android 5.0(API Level 21)之前的平臺版本使用 Dalvik 運行時來執行應用代碼。默認情況下,Dalvik 將應用限制為每個 APK 只能使用一個 classes.dex 字節碼文件。要繞過這一限制,您可以在您的項目中添加多 dex 文件支持庫:
dependencies {
implementation 'androidx.multidex:multidex:2.0.1'
}
如果您不使用 AndroidX,請改為添加以下支持庫依賴項:
dependencies {
implementation 'com.android.support:multidex:1.0.3'
}
此庫會成為應用的主要 DEX 文件的一部分,然后管理對其他 DEX 文件及其包含的代碼訪問。如需了解詳情,請參與下面有關下面介紹針對多 dex 文件配置您的應用的部分。
- 注意:如果為您的項目配置的多 dex 文件使用的是 minSdkVersion 20 或更低版本,而您將其部署到運行 Android 4.4(API Level 20)或更低版本系統的目標設備上,則 Android Studio 會停用 Instant Run。
Android 5.0 及更高版本的多 dex 文件支持
Android 5.0(API 級別 21)及更高版本使用名為 ART 的運行時,它本身支持從 APK 文件加載多個 DEX 文件。ART 在應用安裝時執行預編譯,掃描 classesN.dex 文件,并將它們編譯成單個 .oat 文件,以供 Android 設備執行。因此,如果您的 minSdkVersion 為 21 或更高的值,則不需要多 dex 文件支持庫。
- 注意:使用 Instant Run 時,如果將應用的 minSdkVersion 設為 21 或更高的值,Android Studio 會自動針對多 dex 文件配置您的應用。由于 Instant Run 僅適用于調試版本的應用,因此您仍然需要針對多個 dex 文件配置發布版本,以規避 64K 限制。
規避 64K 限制
在將您的應用配置為支持使用 64K 或更多方法引用之前,您應該采取措施來減少應用代碼調用的引用總數,包括由您的應用代碼或包含的庫定義的方法。以下策略可幫助您避免達到 DEX 引用限制:
檢查應用的直接和傳遞依懶性 - 確保您在應用中使用任何龐大依賴庫所帶來的好處多于為應用添加大量代碼所帶來的弊端。一種常見的反面模式是,僅僅為了使用幾個實用方法就在應用中加入非常龐大的庫。減少您的應用代碼依賴項往往能夠幫助您規避 DEX 引用限制。
通過 ProGuard 移除未使用的代碼 - 啟用代碼壓縮,以便對您的發布版本運行 ProGuard。啟用代碼壓縮,以便對您的發布版本運行 ProGuard。啟用壓縮可確保您交付的 APK 不含有未使用的代碼。
使用這些技巧使您不必在應用中應用多 dex 文件,同時還會減少 APK 的總大小。
針對多 dex 文件配置您的應用
將您的應用項目設為使用多 dex 文件配置要求您對應用項目進行以下修改,具體取決于應用支持最低 Android 版本。如果您的 minSdkVersion 設為 21 或更高的值,您只需要在模塊級 build.gradle 文件中將 multiDexEnabled 設為 true,如下所示:
android {
defaultConfig {
...
minSdkVersion 21
targetSdkVersion 28
multiDexEnabled true
}
...
}
不過,如果您的 minSdkVersion 設為 20 或更低的值,則必須使用多 dex 文件支持庫,具體操作步驟如下:
- 修改模塊機 build.gradle 文件以啟用多 dex 文件,并將多 dex 文件庫添加為依賴項,如下所示:
android {
defaultConfig {
...
minSdkVersion 15
targetSdkVersion 28
multiDexEnabled true
}
...
}
dependencies {
compile 'com.android.support:multidex:1.0.3'
}
- 根據是否替換
Application
類,執行以下某項操作:
-
如果您不替換
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(如果可能),如下所示:<?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
類,但無法更改基類,則可以改為替換attachBaseContext()
方法并調用MultiDex.install(this)
來啟用多 dex 文件:
public class MyApplication extends SomeOtherApplication {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
}
-
注意:在
MultiDex.install()
完成之前,不要通過反射或 JNI 執行MultiDex.install()
或其他任何代碼。多 dex 文件跟蹤功能不會追蹤這些調用,從而導致出現ClassNotFoundException
,或因 DEX 文件之間的類分區錯誤而導致驗證錯誤。
現在,當您編譯應用時,Android 編譯工具會根據需要構造主要 DEX 文件(classes.dex)和 輔助 DEX 文件(classes2.dex 和 classes3.dex 等)。然后,編譯系統會將所有 DEX 文件打包到您的 APK 中。
在運行時,多 dex 文件 API 使用特殊的類加載器來搜索適用于您的方法的所有 DEX 文件(而不是只在主 classes.dex 文件中搜索)。
多 dex 文件支持庫的局限性
多 dex 文件支持庫具有一些已知的局限性,將其納入您的以你雇傭編譯配置時,您應注意這些局限性并進行針對的測試:
啟動期間在設備的數據分區上安裝 DEX 文件的過程相當復雜,如果輔助 DEX 文件較大,可能會導致應用無響應(ANR)錯誤。在這種情況下,您應通過 ProGuard 應用代碼壓縮,以盡量減小 DEX 文件的大小,并移除未使用的那部分代碼。
當運行的版本低于 Android 5.0(API Level 21)時,使用多 dex 文件不足以避開 linearalloc 限制(問題 78035)。此上線在 Android 4.0(API 級別 14)中有所提高,但這并不未完全解決該問題。在低于 Android 4.0 的版本中,您可能會在達到 DEX 索引限制之前達到 linearalloc 限制。因此,如果您的目標 API 級別低于 14,請在這些版本的平臺上進行全面測試,因為您的應用可能會在啟動時或加載特定類組時出現問題。
代碼壓縮課件較少甚至有可能消除這些問題。
聲明主要 DEX 文件中必需的類
為多 dex 文件應用編譯每個 DEX 文件時,編譯工具會執行復雜的決策制定來確定主要 DEX 文件中需要的類,以便應用能夠成功啟動。如果主要 DEX 文件中未提供啟動期間需要的任何類,則應用會崩潰并出現 java.lang.NoClassDefFoundError 錯誤。
對于直接從您的應用代碼訪問代碼,不應發生這種情況,因為編譯工具可以識別這些代碼路徑。但是,當代碼路徑的可見性較低時(例如,當您使用的庫具有復雜的依賴項時),可能會發生這種情況。例如,如果代碼使用自檢機制或從原生代碼調用 Java 方法,那么可能不會將這些類識別為主要 DEX 文件中必需類。
因此,如果您收到 java.lang.NoClassDefFoundError,則必須使用版本類型中的 multiDexKeepFile 或 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 相同格式,并且支持全部 Progurad 語法。如需詳細了解 Proguard 格式和語法,請參閱 Proguard 手冊中的 Keep 選項一節。
您在 multiDexKeepProjuard 中指定的文件應該在任何有效的 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 file('multidex-config.pro')
...
}
}
}
在開發編譯中優化多 dex 文件
多 dex 文件配置會大幅增加編譯時間,因為編譯系統必須就那些類必須包含在主要 DEX 文件中以及哪些類可以包含在輔助 DEX 文件中做出復雜的決策。這意味著,使用多 dex 文件的增量編譯通常耗時較長,可能會拖慢您的開發進度。
要縮短較長的增量編譯時間,您應使用 dex 預處理在編譯之間重用多 dex 文件輸出。dex 預處理依賴于一種只在 Android 5.0(API 級別 21)及更高版本中提供的 ART 格式。如果您使用的是 Android Studio 2.3 及更高版本,那么在將您的應用部署到搭載 Android 5.0(API 級別 21)或更高版本的設備上時,IDE 會自動使用此功能。
- 提示:Android Pluging for gradle 3.0.0 及更高版本得到進一步改進來優化編譯速度,如每個類的 dex 處理(這樣,只有您修改的類會重新進行 dex 處理)。一般來說,為了獲得最佳開發體驗,您應該使用升級最新版 Android Studio 和 Android 插件。
不過,如果您是從命令行運行 Gradle 編譯,則需要將 minSdkVersion 設為 21 或更高值以啟用 dex 預處理。要保留正式版的設置,一種有用的策略是使用產品類型(一個開發類型和一個發布類型,它們具有不同的 minSdkVersion
值)來創建兩個應用版本,如下所示。
android {
defaultConfig {
...
multiDexEnabled true
// The default minimum API level you want to support.
minSdkVersion 15
}
productFlavors {
// Includes settings you want to keep only while developing your app.
dev {
// Enables pre-dexing for command line builds. When using
// Android Studio 2.3 or higher, the IDE enables pre-dexing
// when deploying your app to a device running Android 5.0
// (API level 21) or higher—regardless of what you set for
// minSdkVersion.
minSdkVersion 21
}
prod {
// If you've configured the defaultConfig block for the production version of
// your app, you can leave this block empty and Gradle uses configurations in
// the defaultConfig block instead. You still need to include this flavor.
// Otherwise, all variants use the "dev" flavor configurations.
}
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
}
dependencies {
compile 'com.android.support:multidex:1.0.3'
}
要了解有助于改進編譯速度(從 Android Studio 或命令行中)的更多策略,請閱讀優化您的編譯速度。如需詳細了解如何使用編譯變體,請參閱配置編譯變體。
-
提示:由于您有滿足不同多 dex 文件需求的不同編譯變體,因此也可以為不同的變體提供不同的清單文件(這樣,只有適用于 API 級別 20 及更低級別的清單文件會更改 <application> 標記名稱),或者為每個變體創建不同的 Application 子類(這樣,只有適用于 API 級別 20 及更低級別的子類會擴展
MultiDexApplication
類或調用MultiDex.install(this)
)
測試多 dex 文件應用
編寫多 dex 文件應用的插樁測試時,如果使用 MonitoringInstrumentation
(或 AndroidJUnitRunner
)插樁測試,則不需要額外的配置。如果使用其他 Instrumentation
,則必須將其 onCreate()
方法替換為以下代碼:
public void onCreate(Bundle arguments) {
MultiDex.install(getTargetContext());
super.onCreate(arguments);
...
}
☆注意:
請勿使用已棄用的
MultiDexTestRunner
,請改用AndroidJUnitRunner
。目前不支持使用多 dex 文件創建測試 APK。