為使用超過 64K 個方法的應用程式啟用 Multidex

如果應用程式的 minSdk API 為 API 20 以下,而且您的應用程式和 程式庫參照超過 65,536 個方法時,遇到下列建構錯誤 表示應用程式已達 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) 位元碼檔案中的程式碼可叫用的參照總數。本資訊頁面說明如何藉由啟用稱之為 Multidex 的應用程式設定,讓應用程式建構及讀取多個 DEX 檔案,以突破這項限制。

關於 64K 參考限制

Android 應用程式 (APK) 檔案包含可執行位元碼檔案,其格式為 Dalvik Executable (DEX) 檔案,其中包含用於執行應用程式的已編譯程式碼。Dalvik Executable 規格限制了單一 DEX 檔案中可參照的方法總數,上限為 65,536 個,包括 Android 架構方法、程式庫方法,以及您自己的程式碼中的方法。

在 「kilo (簡稱 K)」這個字詞表示 1024 年 ( 2^10)。由於 65,536 等於 64 x 1024,因此這項限制稱為 _64K 參考上限_。

Android 5.0 之前版本的 Multidex 支援

Android 5.0 (API 級別 21) 之前的平台版本使用 Dalvik 執行階段來執行應用程式程式碼。根據預設,Dalvik 會限制應用程式為各 APK 的單一 classes.dex 位元碼檔案。如要突破此限制,請將 Multidex 程式庫新增至模組層級的 build.gradlebuild.gradle.kts 檔案:

GroovyKotlin
dependencies {
    def multidex_version = "2.0.1"
    implementation "androidx.multidex:multidex:$multidex_version"
}
dependencies {
    val multidex_version = "2.0.1"
    implementation("androidx.multidex:multidex:$multidex_version")
}

這個程式庫會成為應用程式主要 DEX 檔案的一部分, 管理對其他 DEX 檔案及其所含程式碼的存取權。 如要查看這個程式庫目前的版本,請參閱 Multidex 版本

詳情請參閱「 設定用於 Multidex 的應用程式

Android 5.0 以上版本的 Multidex 支援

Android 5.0 (API 級別 21) 以上版本使用名為 ART 的執行階段, 原生支援從 APK 檔案載入多個 DEX 檔案。ART 會在應用程式安裝時執行預先編譯,掃描 classesN.dex 檔案並編譯成單一 OAT 檔案,讓 Android 裝置執行。因此,如果您的 minSdkVersion 為 21 以上,系統會預設啟用 Multidex,您就不需使用 Multidex 程式庫。

詳情請參閱 Android 5.0 版 請詳閱 Android 執行階段 (ART) 與 Dalvik

注意:使用 Android Studio 執行應用程式時,建構項目會針對您部署的目標裝置進行最佳化。這包括在目標裝置執行時啟用 Multidex Android 5.0 以上版本。由於只有使用 Android Studio 部署應用程式時才會套用這項最佳化設定,因此您可能仍需為 Multidex 設定發布子版本,才能避免達到 64K 上限。

避免 64K 限制

在設定應用程式以使用 64K 個以上的方法參照之前,請先採取下列步驟 ,減少應用程式程式碼呼叫的參照總數,包括 您的應用程式程式碼或隨附的程式庫

下列策略有助於避免達到 DEX 參照上限:

查看應用程式的直接與遞移依附元件
請思考應用程式中任何大型程式庫依附元件的價值,是否高於程式碼的數量 應用程式。其中一個常見但有問題的模式 就是納入一個超大型的程式庫 因為有些公用程式方法相當實用減少應用程式程式碼依附元件通常有助於解決問題 即可避免達到 DEX 參照上限
使用 R8 移除未使用的程式碼
啟用程式碼縮減功能以執行 R8 為發布子版本請啟用縮減功能,確保您 不會透過您的 APK 傳送未使用的代碼。如果此功能經過妥善設定,也可以從依附元件中移除未使用的程式碼和資源。

一旦使用這些技術,您就能縮減 APK 的整體大小,也不必在應用程式中啟用 Multidex。

設定用於 Multidex 的應用程式

注意:如果 minSdkVersion 設為 21 以上,系統會預設啟用 Multidex,而且您不需使用 Multidex 程式庫。

如果 minSdkVersion 設為 20 以下, 必須使用 Multidex 程式庫,然後 以下修改您的應用程式專案:

  1. 將模組層級 build.gradle 檔案修改為 啟用 Multidex,並將 Multidex 程式庫新增為依附元件,如下所示:

    GroovyKotlin
    android {
        defaultConfig {
            ...
            minSdkVersion 15 
            targetSdkVersion 33
            multiDexEnabled true
        }
        ...
    }
    
    dependencies {
        implementation "androidx.multidex:multidex:2.0.1"
    }
    android {
        defaultConfig {
            ...
            minSdk = 15 
            targetSdk = 33
            multiDexEnabled = true
        }
        ...
    }
    
    dependencies {
        implementation("androidx.multidex:multidex:2.0.1")
    }
  2. 視您是否要覆寫 Application 而定 類別時,請執行下列其中一項操作:
    • 如果您未覆寫 Application 類別,請編輯資訊清單檔案,在以下位置設定 android:name<application> 標記如下:

      <?xml version="1.0" encoding="utf-8"?>
      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.myapp">
          <application
                  android:name="androidx.multidex.MultiDexApplication" >
              ...
          </application>
      </manifest>
    • 如果覆寫 Application 類別,請將該類別變更為擴充 MultiDexApplication,如下所示:

      KotlinJava
      class MyApplication : MultiDexApplication() {...}
      public class MyApplication extends MultiDexApplication { ... }
    • 如果您覆寫 Application 但無法變更基礎類別,則 而是覆寫 attachBaseContext() 方法,並呼叫 MultiDex.install(this) 以啟用 multidex:

      KotlinJava
      class MyApplication : SomeOtherApplication() {
      
          override fun attachBaseContext(base: Context) {
              super.attachBaseContext(base)
              MultiDex.install(this)
          }
      }
      public class MyApplication extends SomeOtherApplication {
        @Override
        protected void attachBaseContext(Context base) {
           super.attachBaseContext(base);
           MultiDex.install(this);
        }
      }

      注意:MultiDex.install() 完成前,請勿透過反射機制或 JNI 執行 MultiDex.install() 或者任何其他程式碼。Multidex 追蹤不會追蹤這些呼叫,導致因 DEX 檔案之間不良類別分區的情況造成 ClassNotFoundException 或驗證錯誤。

現在當您建構應用程式時,Android 建構工具會建構主要 DEX 檔案 (classes.dex) 和支援的 DEX 檔案 (classes2.dexclasses3.dex 等)。 接著,建構系統會將所有 DEX 檔案封裝在您的 APK 中。

從執行階段開始,而不是只在主要 classes.dex 檔案時,Multidex API 會使用特殊的類別載入器來搜尋所有 您方法可用的 DEX 檔案。

Multidex 程式庫的限制

Multidex 程式庫有一些已知的限制。 將此程式庫加入應用程式建構設定前,請考量下列事項:

  • 在啟動期間將 DEX 檔案安裝至裝置資料分區的程序相當複雜, 如果次要 DEX 檔案過大,可能會導致應用程式無回應 (ANR) 錯誤。目的地: 如要避免這個問題,請啟用程式碼縮減功能,將發生的問題 ,並移除未使用的程式碼部分。
  • 在 Android 5.0 (API 級別 21) 之前的版本中執行時,請使用 Multidex 不足以解決 Linearalloc 限制 (問題 37008143)。此上限已在 Android 4.0 (API 級別 14) 中提高,但仍未完全解決問題。

    在低於 Android 4.0 的版本中,您可能會在達到 DEX 參照上限前就先達到 LinearAlloc 上限。因此,如果您指定的 API 級別低於 14,請在這些平台版本中進行完整測試,因為您的應用程式可能會在啟動期間或載入特定類別群組時發生問題。

    程式碼縮減可以減少或排除這類問題。

宣告主要 DEX 檔案中所需的類別

為 Multidex 應用程式建構每個 DEX 檔案時,建構工具會執行以下作業 複雜的決策流程,判斷主要 DEX 中需要哪些類別 檔案,您的應用程式才能順利啟動。如果在啟動期間主要 DEX 檔案中並未提供任何必要類別,您的應用程式就會停止運作並傳回 java.lang.NoClassDefFoundError 錯誤。

針對直接從應用程式存取的程式碼,建構工具會辨識程式碼路徑 再也不是件繁重乏味的工作不過,這個問題 程式碼路徑較不可見時 (例如您使用的程式庫 複雜的依附元件舉例來說,如果程式碼使用自我檢查或叫用 因此,系統可能不會將這些類別視為 必須安裝於主要 DEX 檔案中

如果收到 java.lang.NoClassDefFoundError,您必須手動指定主要 DEX 檔案內所需的其他類別,做法是在建構類型中使用 multiDexKeepProguard 屬性宣告這些類別。如果類別相符 multiDexKeepProguard 檔案,然後是該類別 就會新增至主要 DEX 檔案。

multiDexKeepProguard 屬性

multiDexKeepProguard 檔案採用與 ProGuard 相同的格式,並且支援所有 ProGuard 文法。如要進一步瞭解如何自訂應用程式中保留的程式碼,請參閱「自訂要保留的程式碼」。

您在 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

接著,您可以針對建構類型宣告該檔案,如下所示:

GroovyKotlin
android {
    buildTypes {
        release {
            multiDexKeepProguard file('multidex-config.pro')
            ...
        }
    }
}
android {
    buildTypes {
        getByName("release") {
            multiDexKeepProguard = file("multidex-config.pro")
            ...
        }
    }
}

在開發版本中最佳化 Multidex

Multidex 設定需要大幅增加建構處理能力 因為建構系統必須對哪些類別做出複雜決策 必須包含在主要 DEX 檔案中,以及可將哪些類別納入 次要 DEX 檔案。這表示通常使用 Multidex 漸進式建構作業 可能會拖慢開發程序

如要縮短較長的漸進式建構時間,請採用「DEX 前置處理」程序,在建構作業之間重複使用 Multidex 輸出內容。DEX 前置處理仰賴 ART 格式,僅支援 Android 5.0 (API 級別 21) 以上版本。如果您使用的是 Android Studio,IDE 會自動採用 DEX 前置處理 以便將應用程式部署至搭載 Android 5.0 (API 級別 21) 以上版本的裝置。 不過,如果您透過指令列執行 Gradle 建構作業,就必須將 minSdkVersion 設為 21 以上,才能啟用 DEX 前置處理程序。

如要保留您的 正式版,您可以建立兩個應用程式版本 使用變種版本—一個版本 分別是開發變種版本和發布變種版本 minSdkVersion 有不同的值 如下所示:

GroovyKotlin
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 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 {
    implementation "androidx.multidex:multidex:2.0.1"
}
android {
    defaultConfig {
        ...
        multiDexEnabled = true
        // The default minimum API level you want to support.
        minSdk = 15
    }
    productFlavors {
        // Includes settings you want to keep only while developing your app.
        create("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 minSdkVersion.
            minSdk = 21
        }
        create("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 {
        getByName("release") {
            isMinifyEnabled = true
            proguardFiles(getDefaultProguardFile("proguard-android.txt"),
                                                 "proguard-rules.pro")
        }
    }
}

dependencies {
    implementation("androidx.multidex:multidex:2.0.1")
}

如要進一步瞭解透過 Android Studio 或指令列協助改善建構速度的策略,請參閱「最佳化建構速度」。如要進一步瞭解如何使用建構變數,請參閱 設定建構變數

提示:如果您為 Multidex 需求,您可以為每種 因此只有 API 級別 20 以下的檔案會變更 <application> 標記名稱。此外,也可以為每個變數建立不同的 Application 子類別,只讓 API 20 以下級別適用的子類別擴充 MultiDexApplication 類別或呼叫 MultiDex.install(this)

測試 Multidex 應用程式

為 Multidex 應用程式編寫檢測設備測試時,如果使用 MonitoringInstrumentationAndroidJUnitRunner 檢測,就不需要任何額外設定。如果您使用其他 Instrumentation, 接著,您必須使用下列程式碼覆寫其 onCreate() 方法:

KotlinJava
fun onCreate(arguments: Bundle) {
  MultiDex.install(targetContext)
  super.onCreate(arguments)
  ...
}
public void onCreate(Bundle arguments) {
  MultiDex.install(getTargetContext());
  super.onCreate(arguments);
  ...
}