為方法數(shù)超過(guò) 64K 的應(yīng)用啟用多 dex 文件
當(dāng)您的應(yīng)用及其引用的庫(kù)超過(guò) 65536 個(gè)方法時(shí)采章,您會(huì)遇到一個(gè)編譯錯(cuò)誤弧蝇,指明您的應(yīng)用已達(dá)到 Android 編譯架構(gòu)規(guī)定的引用限制:
trouble writing output:
Too many field references: 131000; max is 65536.
You may try using --multi-dex option.
較低版本的編譯系統(tǒng)會(huì)報(bào)告一個(gè)不同的錯(cuò)誤蝶涩,但指示的是同一個(gè)問(wèn)題:
Conversion to Dalvik format failed:
Unable to execute dex: method ID not in [0, 0xffff]: 65536
這種錯(cuò)誤情況都會(huì)顯示一個(gè)共同的數(shù)字:65536骤铃。此數(shù)字表示單個(gè) Dalvik Executable(DEX)字節(jié)碼文件內(nèi)的代碼可調(diào)用的引用總數(shù)沛善。該文章主要介紹如何通過(guò)啟用稱為“多 DEX 文件”的應(yīng)用配置(該配置使您的應(yīng)用能夠編譯和讀取多個(gè) DEX 文件)來(lái)越過(guò)這一限制。
關(guān)于 64K 引用限制
Android 應(yīng)用(APK)文件包含 Dalvik Executable(DEX)文件形式的可執(zhí)行字節(jié)碼文件驶乾,這些文件包含用來(lái)運(yùn)行您的應(yīng)用的已編譯代碼邑飒。Dalvik Executable 規(guī)范將可在單個(gè) DEX 文件內(nèi)引用的方法總數(shù)限制為 65536,其中包括 Android 框架方法级乐、庫(kù)方法以及您自己代碼中的方法疙咸。由于 65536 等于 64 * 1024,因此這一限制成為“64K 引用限制”风科。
Android 5.0 之前版本的多 dex 文件支持
Android 5.0(API Level 21)之前的平臺(tái)版本使用 Dalvik 運(yùn)行時(shí)來(lái)執(zhí)行應(yīng)用代碼撒轮。默認(rèn)情況下,Dalvik 將應(yīng)用限制為每個(gè) APK 只能使用一個(gè) classes.dex 字節(jié)碼文件贼穆。要繞過(guò)這一限制题山,您可以在您的項(xiàng)目中添加多 dex 文件支持庫(kù):
dependencies {
implementation 'androidx.multidex:multidex:2.0.1'
}
如果您不使用 AndroidX,請(qǐng)改為添加以下支持庫(kù)依賴項(xiàng):
dependencies {
implementation 'com.android.support:multidex:1.0.3'
}
此庫(kù)會(huì)成為應(yīng)用的主要 DEX 文件的一部分故痊,然后管理對(duì)其他 DEX 文件及其包含的代碼訪問(wèn)顶瞳。如需了解詳情,請(qǐng)參與下面有關(guān)下面介紹針對(duì)多 dex 文件配置您的應(yīng)用的部分愕秫。
- 注意:如果為您的項(xiàng)目配置的多 dex 文件使用的是 minSdkVersion 20 或更低版本慨菱,而您將其部署到運(yùn)行 Android 4.4(API Level 20)或更低版本系統(tǒng)的目標(biāo)設(shè)備上,則 Android Studio 會(huì)停用 Instant Run戴甩。
Android 5.0 及更高版本的多 dex 文件支持
Android 5.0(API 級(jí)別 21)及更高版本使用名為 ART 的運(yùn)行時(shí)符喝,它本身支持從 APK 文件加載多個(gè) DEX 文件。ART 在應(yīng)用安裝時(shí)執(zhí)行預(yù)編譯甜孤,掃描 classesN.dex 文件协饲,并將它們編譯成單個(gè) .oat 文件,以供 Android 設(shè)備執(zhí)行课蔬。因此囱稽,如果您的 minSdkVersion 為 21 或更高的值,則不需要多 dex 文件支持庫(kù)二跋。
- 注意:使用 Instant Run 時(shí)战惊,如果將應(yīng)用的 minSdkVersion 設(shè)為 21 或更高的值,Android Studio 會(huì)自動(dòng)針對(duì)多 dex 文件配置您的應(yīng)用扎即。由于 Instant Run 僅適用于調(diào)試版本的應(yīng)用吞获,因此您仍然需要針對(duì)多個(gè) dex 文件配置發(fā)布版本,以規(guī)避 64K 限制谚鄙。
規(guī)避 64K 限制
在將您的應(yīng)用配置為支持使用 64K 或更多方法引用之前各拷,您應(yīng)該采取措施來(lái)減少應(yīng)用代碼調(diào)用的引用總數(shù),包括由您的應(yīng)用代碼或包含的庫(kù)定義的方法闷营。以下策略可幫助您避免達(dá)到 DEX 引用限制:
檢查應(yīng)用的直接和傳遞依懶性 - 確保您在應(yīng)用中使用任何龐大依賴庫(kù)所帶來(lái)的好處多于為應(yīng)用添加大量代碼所帶來(lái)的弊端烤黍。一種常見(jiàn)的反面模式是知市,僅僅為了使用幾個(gè)實(shí)用方法就在應(yīng)用中加入非常龐大的庫(kù)。減少您的應(yīng)用代碼依賴項(xiàng)往往能夠幫助您規(guī)避 DEX 引用限制速蕊。
通過(guò) ProGuard 移除未使用的代碼 - 啟用代碼壓縮嫂丙,以便對(duì)您的發(fā)布版本運(yùn)行 ProGuard。啟用代碼壓縮规哲,以便對(duì)您的發(fā)布版本運(yùn)行 ProGuard跟啤。啟用壓縮可確保您交付的 APK 不含有未使用的代碼。
使用這些技巧使您不必在應(yīng)用中應(yīng)用多 dex 文件唉锌,同時(shí)還會(huì)減少 APK 的總大小隅肥。
針對(duì)多 dex 文件配置您的應(yīng)用
將您的應(yīng)用項(xiàng)目設(shè)為使用多 dex 文件配置要求您對(duì)應(yīng)用項(xiàng)目進(jìn)行以下修改,具體取決于應(yīng)用支持最低 Android 版本袄简。如果您的 minSdkVersion 設(shè)為 21 或更高的值腥放,您只需要在模塊級(jí) build.gradle 文件中將 multiDexEnabled 設(shè)為 true,如下所示:
android {
defaultConfig {
...
minSdkVersion 21
targetSdkVersion 28
multiDexEnabled true
}
...
}
不過(guò)绿语,如果您的 minSdkVersion 設(shè)為 20 或更低的值捉片,則必須使用多 dex 文件支持庫(kù),具體操作步驟如下:
- 修改模塊機(jī) build.gradle 文件以啟用多 dex 文件汞舱,并將多 dex 文件庫(kù)添加為依賴項(xiàng),如下所示:
android {
defaultConfig {
...
minSdkVersion 15
targetSdkVersion 28
multiDexEnabled true
}
...
}
dependencies {
compile 'com.android.support:multidex:1.0.3'
}
- 根據(jù)是否替換
Application
類宗雇,執(zhí)行以下某項(xiàng)操作:
-
如果您不替換
Application
類昂芜,請(qǐng)修改清單以設(shè)置 <application> 標(biāo)記中 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
類赔蒲,請(qǐng)對(duì)其進(jìn)行更改以擴(kuò)展 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
類舞虱,但無(wú)法更改基類欢际,則可以改為替換attachBaseContext()
方法并調(diào)用MultiDex.install(this)
來(lái)啟用多 dex 文件:
public class MyApplication extends SomeOtherApplication {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
}
-
注意:在
MultiDex.install()
完成之前,不要通過(guò)反射或 JNI 執(zhí)行MultiDex.install()
或其他任何代碼矾兜。多 dex 文件跟蹤功能不會(huì)追蹤這些調(diào)用损趋,從而導(dǎo)致出現(xiàn)ClassNotFoundException
,或因 DEX 文件之間的類分區(qū)錯(cuò)誤而導(dǎo)致驗(yàn)證錯(cuò)誤椅寺。
現(xiàn)在浑槽,當(dāng)您編譯應(yīng)用時(shí),Android 編譯工具會(huì)根據(jù)需要構(gòu)造主要 DEX 文件(classes.dex)和 輔助 DEX 文件(classes2.dex 和 classes3.dex 等)返帕。然后桐玻,編譯系統(tǒng)會(huì)將所有 DEX 文件打包到您的 APK 中。
在運(yùn)行時(shí)荆萤,多 dex 文件 API 使用特殊的類加載器來(lái)搜索適用于您的方法的所有 DEX 文件(而不是只在主 classes.dex 文件中搜索)镊靴。
多 dex 文件支持庫(kù)的局限性
多 dex 文件支持庫(kù)具有一些已知的局限性,將其納入您的以你雇傭編譯配置時(shí),您應(yīng)注意這些局限性并進(jìn)行針對(duì)的測(cè)試:
啟動(dòng)期間在設(shè)備的數(shù)據(jù)分區(qū)上安裝 DEX 文件的過(guò)程相當(dāng)復(fù)雜偏竟,如果輔助 DEX 文件較大煮落,可能會(huì)導(dǎo)致應(yīng)用無(wú)響應(yīng)(ANR)錯(cuò)誤。在這種情況下苫耸,您應(yīng)通過(guò) ProGuard 應(yīng)用代碼壓縮州邢,以盡量減小 DEX 文件的大小,并移除未使用的那部分代碼褪子。
當(dāng)運(yùn)行的版本低于 Android 5.0(API Level 21)時(shí)量淌,使用多 dex 文件不足以避開(kāi) linearalloc 限制(問(wèn)題 78035)。此上線在 Android 4.0(API 級(jí)別 14)中有所提高嫌褪,但這并不未完全解決該問(wèn)題呀枢。在低于 Android 4.0 的版本中,您可能會(huì)在達(dá)到 DEX 索引限制之前達(dá)到 linearalloc 限制笼痛。因此裙秋,如果您的目標(biāo) API 級(jí)別低于 14,請(qǐng)?jiān)谶@些版本的平臺(tái)上進(jìn)行全面測(cè)試缨伊,因?yàn)槟膽?yīng)用可能會(huì)在啟動(dòng)時(shí)或加載特定類組時(shí)出現(xiàn)問(wèn)題摘刑。
代碼壓縮課件較少甚至有可能消除這些問(wèn)題。
聲明主要 DEX 文件中必需的類
為多 dex 文件應(yīng)用編譯每個(gè) DEX 文件時(shí)刻坊,編譯工具會(huì)執(zhí)行復(fù)雜的決策制定來(lái)確定主要 DEX 文件中需要的類枷恕,以便應(yīng)用能夠成功啟動(dòng)。如果主要 DEX 文件中未提供啟動(dòng)期間需要的任何類谭胚,則應(yīng)用會(huì)崩潰并出現(xiàn) java.lang.NoClassDefFoundError 錯(cuò)誤徐块。
對(duì)于直接從您的應(yīng)用代碼訪問(wèn)代碼,不應(yīng)發(fā)生這種情況灾而,因?yàn)榫幾g工具可以識(shí)別這些代碼路徑胡控。但是,當(dāng)代碼路徑的可見(jiàn)性較低時(shí)(例如旁趟,當(dāng)您使用的庫(kù)具有復(fù)雜的依賴項(xiàng)時(shí))昼激,可能會(huì)發(fā)生這種情況。例如轻庆,如果代碼使用自檢機(jī)制或從原生代碼調(diào)用 Java 方法癣猾,那么可能不會(huì)將這些類識(shí)別為主要 DEX 文件中必需類纬向。
因此吼鱼,如果您收到 java.lang.NoClassDefFoundError雁仲,則必須使用版本類型中的 multiDexKeepFile 或 multiDexKeepProguard 屬性聲明這些其他類遏考。
以手動(dòng)將這些類指定為主要 DEX 文件中的必需類壶笼。如果某個(gè)類在 multiDexKeepFile 或 multiDexKeepProguard 文件中匹配到暖释,則會(huì)將該類添加到主要 DEX 文件坏瞄。
multiDexKeepFile 屬性
您在 multiDexKeepFile 中指定的文件應(yīng)該每行包含一個(gè)類扬跋,并且采用 com/example/MyClass.class 格式。例如拓春,您可以創(chuàng)建一個(gè)名為 multidex-config.txt 的文件释簿,如下所示:
com/example/MyClass.class
com/example/MyOtherClass.class
然后,您可以針對(duì)版本類型聲明該文件硼莽,如下所示:
android {
buildTypes {
release {
multiDexKeepFile file('multidex-config.txt')
...
}
}
}
請(qǐng)注意庶溶,Gradle 會(huì)讀取相對(duì)于 build.gradle 文件路徑,因此如果 multidex-config.txt 與 build.gradle 文件在同一目錄中懂鸵,以上示例將有效偏螺。
multiDexKeepProguard 屬性
multiDexKeepProguard 文件使用與 Proguard 相同格式,并且支持全部 Progurad 語(yǔ)法匆光。如需詳細(xì)了解 Proguard 格式和語(yǔ)法套像,請(qǐng)參閱 Proguard 手冊(cè)中的 Keep 選項(xiàng)一節(jié)。
您在 multiDexKeepProjuard 中指定的文件應(yīng)該在任何有效的 ProGuard 語(yǔ)法中包含 -keep 選項(xiàng)终息。例如夺巩,-keep com.example.MyClass.class。您可以創(chuàng)建一個(gè)名為 multidex-config.pro 的文件周崭,如下所示:
-keep class com.example.MyClass
-keep class com.example.MyClassToo
如果您要指定軟件包中的所有類柳譬,文件將如下所示:
-keep class com.example.** { *; } // All classes in the com.example package
然后,您可以針對(duì)版本類型聲明該文件续镇,如下所示:
android {
buildTypes {
release {
multiDexKeepProguard file('multidex-config.pro')
...
}
}
}
在開(kāi)發(fā)編譯中優(yōu)化多 dex 文件
多 dex 文件配置會(huì)大幅增加編譯時(shí)間征绎,因?yàn)榫幾g系統(tǒng)必須就那些類必須包含在主要 DEX 文件中以及哪些類可以包含在輔助 DEX 文件中做出復(fù)雜的決策。這意味著磨取,使用多 dex 文件的增量編譯通常耗時(shí)較長(zhǎng),可能會(huì)拖慢您的開(kāi)發(fā)進(jìn)度柴墩。
要縮短較長(zhǎng)的增量編譯時(shí)間忙厌,您應(yīng)使用 dex 預(yù)處理在編譯之間重用多 dex 文件輸出。dex 預(yù)處理依賴于一種只在 Android 5.0(API 級(jí)別 21)及更高版本中提供的 ART 格式江咳。如果您使用的是 Android Studio 2.3 及更高版本逢净,那么在將您的應(yīng)用部署到搭載 Android 5.0(API 級(jí)別 21)或更高版本的設(shè)備上時(shí),IDE 會(huì)自動(dòng)使用此功能歼指。
- 提示:Android Pluging for gradle 3.0.0 及更高版本得到進(jìn)一步改進(jìn)來(lái)優(yōu)化編譯速度爹土,如每個(gè)類的 dex 處理(這樣,只有您修改的類會(huì)重新進(jìn)行 dex 處理)踩身。一般來(lái)說(shuō)胀茵,為了獲得最佳開(kāi)發(fā)體驗(yàn),您應(yīng)該使用升級(jí)最新版 Android Studio 和 Android 插件挟阻。
不過(guò)琼娘,如果您是從命令行運(yùn)行 Gradle 編譯峭弟,則需要將 minSdkVersion 設(shè)為 21 或更高值以啟用 dex 預(yù)處理。要保留正式版的設(shè)置脱拼,一種有用的策略是使用產(chǎn)品類型(一個(gè)開(kāi)發(fā)類型和一個(gè)發(fā)布類型瞒瘸,它們具有不同的 minSdkVersion
值)來(lái)創(chuàng)建兩個(gè)應(yīng)用版本,如下所示熄浓。
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'
}
要了解有助于改進(jìn)編譯速度(從 Android Studio 或命令行中)的更多策略情臭,請(qǐng)閱讀優(yōu)化您的編譯速度。如需詳細(xì)了解如何使用編譯變體赌蔑,請(qǐng)參閱配置編譯變體俯在。
-
提示:由于您有滿足不同多 dex 文件需求的不同編譯變體,因此也可以為不同的變體提供不同的清單文件(這樣惯雳,只有適用于 API 級(jí)別 20 及更低級(jí)別的清單文件會(huì)更改 <application> 標(biāo)記名稱)朝巫,或者為每個(gè)變體創(chuàng)建不同的 Application 子類(這樣,只有適用于 API 級(jí)別 20 及更低級(jí)別的子類會(huì)擴(kuò)展
MultiDexApplication
類或調(diào)用MultiDex.install(this)
)
測(cè)試多 dex 文件應(yīng)用
編寫(xiě)多 dex 文件應(yīng)用的插樁測(cè)試時(shí)石景,如果使用 MonitoringInstrumentation
(或 AndroidJUnitRunner
)插樁測(cè)試劈猿,則不需要額外的配置。如果使用其他 Instrumentation
潮孽,則必須將其 onCreate()
方法替換為以下代碼:
public void onCreate(Bundle arguments) {
MultiDex.install(getTargetContext());
super.onCreate(arguments);
...
}
☆注意:
請(qǐng)勿使用已棄用的
MultiDexTestRunner
揪荣,請(qǐng)改用AndroidJUnitRunner
。目前不支持使用多 dex 文件創(chuàng)建測(cè)試 APK往史。