針對使用超過 64K 方法的應用程式啟用 Multidex

當您的應用程式及其參照的程式庫超過 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 執行檔規格限制在單一 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.gradle 檔案:

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

如要查看這個程式庫的目前版本,請參閱版本頁面的 Multidex 相關資訊。

如果您並非使用 AndroidX,請新增下列已淘汰的支援程式庫依附元件:

Groovy

dependencies {
    implementation 'com.android.support:multidex:1.0.3'
}

Kotlin

dependencies {
    implementation("com.android.support:multidex:1.0.3")
}

這個程式庫會成為應用程式主要 DEX 檔案的一部分,然後管理其他 DEX 檔案及其包含程式碼的存取權限。詳情請參閱下文,瞭解如何設定用於 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 執行階段,請參閱 ART 和 Dalvik

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

避免 64K 限制

在設定應用程式以使用 64K 或以上方法的參照之前,建議您先採取行動,減少應用程式程式碼參照的總數,包括應用程式程式碼或內含程式庫定義的方法。下列策略有助於避免達到 DEX 參照上限:

  • 審查應用程式直接及傳輸依附元件:請確保您在應用程式中加入的任何大型程式庫依附元件使用方式,用量皆超過新增至應用程式中的程式碼數量。常見的反面模式為加入一個超大型的資料庫,因為有些公用程式方法相當實用。減少應用程式程式碼依附元件通常有助於避免 DEX 參照限制。
  • 使用 R8 移除未使用的程式碼:為發布子版本啟用程式碼縮減功能,以執行 R8。啟用縮減功能可確保您不會透過 APK 傳送未使用的程式碼。

這些技術可協助您避免在應用程式中啟用 Multidex,同時也可降低 APK 的整體大小。

設定用於 Multidex 的應用程式

如果 minSdkVersion 設為 21 以上,根據預設會啟用 Multidex,且不需要 Multidex 程式庫。

不過,如果您的 minSdkVersion 設為 20 以下,則必須使用 Multidex 程式庫,並對應用程式專案進行下列修改:

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

    Groovy

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

    Kotlin

    android {
        defaultConfig {
            ...
            minSdk = 15
            targetSdk = 28
            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 限制 (問題 78035)。這項限制已在 Android 4.0 (API 級別 14) 中提高,但仍未完全解決。而在執行 Android 4.0 以下版本的裝置上,您可能會先達到 Linearalloc 限制,再達到 DEX 索引限制。因此,如果您指定的 API 級別低於 14,必須對這些版本進行完整測試,因為您的應用程式可能會在啟動期間或載入特定類別群組時發生問題。

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

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

為 Multidex 應用程式建構各 DEX 檔案時,建構工具會進行複雜決策,決定在主要 DEX 檔案中需要哪些類別,讓應用程式能夠成功啟動。如果在啟動期間主要 DEX 檔案中並未提供任何必要類別,您的應用程式就會停止運作並傳回 java.lang.NoClassDefFoundError 錯誤。

直接從應用程式程式碼存取的程式碼不會發生這類情況,因為建構工具會辨識這些程式碼路徑,但程式碼路徑較不易察覺 (例如您使用的程式庫有複雜依附元件)。舉例來說,如果程式碼使用原生程式碼的自我檢查或叫用 Java 方法,這些類別可能就無法在主要 DEX 檔案中辨識為必要項目。

所以如果您收到 java.lang.NoClassDefFoundError,那麼您必須在主要 DEX 檔案中,藉由建構類型中的 multiDexKeepFilemultiDexKeepProguard 進行宣告。如果在 multiDexKeepFilemultiDexKeepProguard 檔案中的類別相符,系統會將該類別新增至主要 DEX 檔案。

multiDexKeepFile 屬性

您在 multiDexKeepFile 中指定的檔案,每一行應包含一個類別,並採用 com/example/MyClass.class 格式。舉例來說,您可以建立一個名為 multidex-config.txt 的檔案,如下所示:

com/example/MyClass.class
com/example/MyOtherClass.class

接著,您可以宣告建構類型的檔案如下:

Groovy

android {
    buildTypes {
        release {
            multiDexKeepFile file('multidex-config.txt')
            ...
        }
    }
}

Kotlin

android {
    buildTypes {
        getByName("release") {
            multiDexKeepFile = file('multidex-config.txt')
            ...
        }
    }
}

請注意,Gradle 會讀取相對於 build.gradle 檔案的路徑,因此若 multidex-config.txt 位在和 build.gradle 檔案相同的目錄中時,上述範例才有效。

multiDexKeepProguard 屬性

multiDexKeepProguard 檔案的格式與 Proguard 相同,並且支援整個 Proguard 文法。如要進一步瞭解 Proguard 格式和文法,請參閱 Proguard 手冊中的 Keep 選項一節。

您在 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 輸出。DEX 前置處理仰賴 ART 格式,僅支援 Android 5.0 (API 級別 21) 以上版本。如果您使用的是 Android Studio 2.3 以上版本,IDE 會在部署您的應用程式至執行 Android 5.0 (API 級別 21) 以上版本的裝置時自動使用此功能。

提示: Gradle 3.0.0 以上適用的 Android 外掛程式包括進一步改善最佳化建構速度,例如各類別的 Dex 處理 (只會重新 DEX 處理您修改的類別)。一般來說,為獲得最佳開發體驗,請一律升級至最新版本的 Android Studio 和 Android 外掛程式。

不過,如果您是透過指令列執行 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 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 {
    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 what you set for
            // 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);
  ...
}