为方法数超过 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 可执行 (DEX) 字节码文件中的代码可以调用的引用总数。本页说明如何通过启用称为 _multidex_ 的应用配置来克服此限制,这使您的应用能够构建和读取多个 DEX 文件。

关于 64K 引用限制

Android 应用 (APK) 文件包含 Dalvik 可执行 (DEX) 文件形式的可执行字节码文件,其中包含用于运行您的应用的已编译代码。Dalvik 可执行规范限制单个 DEX 文件中可以引用的方法总数为 65,536 个,包括 Android 框架方法、库方法和您自己的代码中的方法。

在计算机科学中,术语 _kilo 或 K_ 代表 1024(或 2^10)。由于 65,536 等于 64x1024,因此此限制被称为 _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 的运行时,该运行时原生支持从 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 的总体大小,并避免在您的应用程序中使用多 dex。

为您的应用程序配置多 dex

注意:如果您的 minSdkVersion 设置为 21 或更高版本,则默认情况下启用多 dex,您不需要多 dex 库。

如果您的 minSdkVersion 设置为 20 或更低版本,则您必须使用 多 dex 库 并对您的应用程序项目进行以下修改

  1. 修改模块级别的 build.gradle 文件以启用多 dex 并将多 dex 库添加为依赖项,如下所示

    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 类,请编辑您的清单文件以设置 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,如下所示

      Kotlin

      class MyApplication : MultiDexApplication() {...}
      

      Java

      public class MyApplication extends MultiDexApplication { ... }
      
    • 如果您重写了 Application 类,但无法更改基类,则可以覆盖 attachBaseContext() 方法并调用 MultiDex.install(this) 以启用多 dex

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

      注意: 不要通过反射或 JNI 在 MultiDex.install() 完成之前执行 MultiDex.install() 或任何其他代码。多 dex 跟踪不会跟踪这些调用,会导致 ClassNotFoundException 或由于 DEX 文件之间类分区错误而导致的验证错误。

现在,当您构建应用程序时,Android 构建工具会构建一个主 DEX 文件(classes.dex)和辅助 DEX 文件(classes2.dexclasses3.dex 等),根据需要。然后,构建系统会将所有 DEX 文件打包到您的 APK 中。

在运行时,多 dex API 不仅在主 classes.dex 文件中搜索,还会使用一个特殊的类加载器来搜索所有可用的 DEX 文件以找到您的方法。

多 dex 库的局限性

多 dex 库有一些已知的局限性。当您将库合并到应用程序构建配置中时,请考虑以下事项

  • 在启动时将 DEX 文件安装到设备的数据分区很复杂,如果辅助 DEX 文件很大,可能会导致应用程序无响应 (ANR) 错误。为了避免此问题,请 启用代码缩减 以最大程度地减少 DEX 文件的大小并删除未使用的代码部分。
  • 在 Android 5.0(API 级别 21)之前的版本上运行时,使用多 dex 不足以解决 linearalloc 限制(问题 37008143)。此限制在 Android 4.0(API 级别 14)中有所增加,但这并不能完全解决问题。

    在低于 Android 4.0 的版本上,您可能会在达到 DEX 索引限制之前达到 linearalloc 限制。因此,如果您要定位低于 14 的 API 级别,请在这些平台版本上进行全面测试,因为您的应用程序在启动时或加载特定类组时可能会出现问题。

    代码缩减 可以减少或消除这些问题。

声明主 DEX 文件中所需的类

构建多 dex 应用程序的每个 DEX 文件时,构建工具会执行复杂的决策过程来确定哪些类需要在主 DEX 文件中,以便您的应用程序可以成功启动。如果启动过程中所需的任何类未在主 DEX 文件中提供,则您的应用程序会因错误而崩溃 java.lang.NoClassDefFoundError

构建工具识别从应用程序代码直接访问的代码的代码路径。但是,当代码路径不太明显时,例如您使用的库具有复杂的依赖项时,可能会发生此问题。例如,如果代码使用来自本机代码的 Java 方法的内省或调用,那么这些类可能不被识别为主 DEX 文件中必需的类。

如果您收到 java.lang.NoClassDefFoundError,您必须通过使用 multiDexKeepProguard 属性在构建类型中声明它们,手动指定主 DEX 文件中所需的附加类。如果在 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")
            ...
        }
    }
}

优化开发版本中的多 dex

多 dex 配置需要显着增加构建处理时间,因为构建系统必须做出复杂的决策,以确定哪些类必须包含在主 DEX 文件中,哪些类可以包含在辅助 DEX 文件中。这意味着使用多 dex 的增量构建通常需要更长时间,并且可能会减慢您的开发过程。

为了减轻增量构建时间过长的问题,请使用预 dex 在构建之间重用多 dex 输出。预 dex 依赖于仅在 Android 5.0(API 级别 21)及更高版本上可用的 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 或命令行提高构建速度的更多策略,请阅读 优化构建速度。有关使用构建变体的更多信息,请参阅 配置构建变体

提示:如果您针对不同的多 dex 需求具有不同的构建变体,则可以为每个变体提供不同的清单文件,以便只有针对 API 级别 20 及更低版本的变体才会更改 <application> 标签名称。您还可以为每个变体创建一个不同的 Application 子类,以便只有针对 API 级别 20 及更低版本的子类会扩展 MultiDexApplication 类或调用 MultiDex.install(this)

测试多 dex 应用程序

当您为多 dex 应用程序编写工具测试时,如果您使用 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);
  ...
}