為使用超過 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 Executable (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 追蹤功能不會追蹤這些呼叫,導致 DEX 檔案之間的不良類別分區造成 ClassNotFoundException 或驗證錯誤。

現在當您建構應用程式時,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 應用程式編寫檢測設備測試時,如果使用 MonitoringInstrumentationAndroidJUnitRunner 檢測,就不需要任何額外設定。若使用其他 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);
  ...
}