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

如果您應用程式的 minSdk 為 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 執行檔 (DEX) 格式的可執行位元碼檔案,其中包含用於執行應用程式的編譯程式碼。Dalvik Executable 規格會將單一 DEX 檔案中可參照的方法總數限制為 65,536 個,包括 Android 架構方法、程式庫程式碼及方法中的方法。

以電腦科學來說,「kilo (簡稱 K)一詞表示 1024 (或 2^10)。由於 65,536 等於 64 乘以 1024,因此這項限制稱為「64K 參照上限」。

Android 5.0 之前版本的 Multidex 支援

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

Groovy

dependencies {
    def multidex_version = "2.0.1"
    implementation "androidx.multidex:multidex:$multidex_version"
}

Kotlin

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 的執行階段,ART 原生支援從 APK 檔案載入多個 DEX 檔案。ART 會在應用程式安裝時執行預先編譯,掃描 classesN.dex 檔案並編譯成單一 OAT 檔案,讓 Android 裝置執行。因此,如果您的 minSdkVersion 為 21 以上,系統會預設啟用 Multidex,您就不需使用 Multidex 程式庫。

如要進一步瞭解 Android 5.0 執行階段,請參閱「Android 執行階段 (ART) 與 Dalvik」。

注意:使用 Android Studio 執行應用程式時,建構作業會針對您部署的目標裝置進行最佳化處理。這包括在目標裝置搭載 Android 5.0 以上版本時啟用 Multidex。由於只有使用 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 程式庫新增為依附元件,如下所示:

    Groovy

    android {
        defaultConfig {
            ...
            minSdkVersion 15 
            targetSdkVersion 33
            multiDexEnabled true
        }
        ...
    }
    
    dependencies {
        implementation "androidx.multidex:multidex:2.0.1"
    }
    

    Kotlin

    android {
        defaultConfig {
            ...
            minSdk = 15 
            targetSdk = 33
            multiDexEnabled = true
        }
        ...
    }
    
    dependencies {
        implementation("androidx.multidex:multidex:2.0.1")
    }
    
  2. 視您是否要覆寫 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="androidx.multidex.MultiDexApplication" >
              ...
          </application>
      </manifest>
      
    • 如果覆寫 Application 類別,請將該類別變更為擴充 MultiDexApplication,如下所示:

      Kotlin

      class MyApplication : MultiDexApplication() {...}
      

      Java

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

      Kotlin

      class MyApplication : SomeOtherApplication() {
      
          override fun attachBaseContext(base: Context) {
              super.attachBaseContext(base)
              MultiDex.install(this)
          }
      }
      

      Java

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

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

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

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

Multidex 程式庫的限制

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

  • 在啟動期間安裝 DEX 檔案至裝置資料分區的程序相當複雜,如果次要 DEX 檔案偏大,可能會導致應用程式無回應 (ANR) 錯誤。如要避免這個問題,請啟用程式碼縮減功能,盡可能減少 DEX 檔案的大小,並移除未使用的程式碼部分。
  • 在 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 錯誤。

針對透過應用程式中程式碼直接存取的程式碼,建構工具會辨識程式碼路徑。不過,若程式碼路徑較不容易辨識 (例如您使用的程式庫包含複雜依附元件),仍可能發生這項問題。舉例來說,如果程式碼使用原生程式碼的自我檢查或叫用 Java 方法,這些類別可能就無法在主要 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

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

Groovy

android {
    buildTypes {
        release {
            multiDexKeepProguard file('multidex-config.pro')
            ...
        }
    }
}

Kotlin

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

在開發版本中最佳化 Multidex

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

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

如要保留正式版本的設定,您可以利用變種版本建立兩個應用程式版本 (分別為開發變種版本和發布變種版本),並設定不同的 minSdkVersion 值,如下所示:

Groovy

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"
}

Kotlin

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 應用程式編寫檢測設備測試時,如果使用 MonitoringInstrumentation AndroidJUnitRunner 檢測,就不需要任何額外設定。如果使用其他 Instrumentation,就必須使用下列程式碼覆寫其 onCreate() 方法:

Kotlin

fun onCreate(arguments: Bundle) {
  MultiDex.install(targetContext)
  super.onCreate(arguments)
  ...
}

Java

public void onCreate(Bundle arguments) {
  MultiDex.install(getTargetContext());
  super.onCreate(arguments);
  ...
}