企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
原文出处[Google官网——配置方法数超过 64K 的应用](https://developer.android.com/tools/building/multidex.html#mdex-gradle) ### **背景**: 随着 Android 平台的持续成长,Android 应用的大小也在增加。当您的应用及其引用的库达到特定大小时,您会遇到构建错误,指明您的应用已达到 Android 应用构建架构的极限。早期版本的构建系统按如下方式报告这一错误: ~~~ Conversion to Dalvik format failed: Unable to execute dex: method ID not in [0, 0xffff]: 65536 ~~~ 较新版本的 Android 构建系统虽然显示的错误不同,但指示的是同一问题: ~~~ trouble writing output: Too many field references: 131000; max is 65536. You may try using --multi-dex option ~~~ 这些错误状况都会显示下面这个数字:65536。这个数字很重要,因为它**代表的是单个 Dalvik Executable (DEX) 字节码文件内的代码可调用的引用总数**。本页介绍**如何通过启用被称为 Dalvik 可执行文件分包的应用配置来越过这一限制**,使您的应用能够构建并读取 Dalvik 可执行文件分包 DEX 文件。 #### **关于 64K 引用限制** Android 应用 (APK) 文件包含 [Dalvik](https://source.android.com/devices/tech/dalvik/) Executable (DEX) 文件形式的可执行字节码文件,其中包含用来运行您的应用的已编译代码。**Dalvik Executable 规范将可在单个 DEX 文件内可引用的方法总数限制在 65,536,其中包括 Android 框架方法、库方法以及您自己代码中的方法**。在计算机科学领域内,术语[千(简称 K)](https://en.wikipedia.org/wiki/Kilo-)表示 1024(或 2^10)。由于 65,536 等于 64 X 1024,因此这一限制也称为“**64K 引用限制**”。 #### **Android 5.0 之前版本的 Dalvik 可执行文件分包支持** Android 5.0(**API 级别 21**)之前的平台版本使用 Dalvik 运行时来执行应用代码。默认情况下,Dalvik 限制应用的每个 APK 只能使用单个 classes.dex 字节码文件。要想绕过这一限制,您可以使用 [Dalvik 可执行文件分包支持库](https://developer.android.com/tools/support-library/features.html#multidex),它会成为您的应用主要 DEX 文件的一部分,然后管理对其他 DEX 文件及其所包含代码的访问。 > **注**:如果您的项目配置时所面向的 Dalvik 可执行文件分包使用的是 minSdkVersion 20 或更低版本,并且您将其部署到运行 Android 4.4(API 级别 20)或更低版本的目标设备上,则 Android Studio 会停用 [Instant Run](https://developer.android.com/tools/building/building-studio.html#instant-run)。 关于 InstantRun可参考——[深度理解Android InstantRun原理以及源码分析](https://github.com/nuptboyzhb/AndroidInstantRun) #### **Android 5.0 及更高版本的 Dalvik 可执行文件分包支持** Android 5.0(API 级别 21)及更高版本使用名为 ART 的运行时(Google在2014年,Google I/O大会,ART才正式取代Dalvik,提出了简单方便的multidex的解决方案),后者**ART原生支持从 APK 文件加载多个 DEX 文件**。**ART 在应用安装时执行预编译,扫描 classesN.dex 文件,并将它们编译成单个 .oat 文件,供 Android 设备执行。因此,如果您的 minSdkVersion 为 21 或更高值,则不需要 Dalvik 可执行文件分包支持库。** 如需了解有关 Android 5.0 运行时的详细信息,请参阅 [ART 和 Dalvik](https://source.android.com/devices/tech/dalvik/art.html)。 > 注:如果将应用的 minSdkVersion 设置为 21 或更高值,使用 Instant Run 时,Android Studio 会自动将应用配置为进行 Dalvik 可执行文件分包。由于 Instant Run 仅适用于调试版本的应用,您仍需配置发布构建进行 Dalvik 可执行文件分包,以规避 64K 限制。 #### **规避 64K 限制(尽量避免超过64K)** 在将您的应用配置为支持使用 64K 或更多方法引用之前,您**应该采取措施减少应用代码调用的引用总数,包括由您的应用代码或包含的库定义的方法**。下列策略可帮助您避免达到 DEX 引用限制: * **检查您的应用的直接和传递依赖项** - 确保您在应用中使用任何庞大依赖库所带来的好处大于为应用添加大量代码所带来的弊端。一种常见的反面模式是,仅仅为了使用几个实用方法就在应用中加入非常庞大的库。**减少您的应用代码依赖项往往能够帮助您规避 dex 引用限制。** * **通过 ProGuard 移除未使用的代码** - 为您的版本构建[启用代码压缩](https://developer.android.com/studio/build/shrink-code.html)以运行 ProGuard。**启用压缩可确保您交付的 APK 不含有未使用的代码。** 使用这些技巧使您不必在应用中启用 Dalvik 可执行文件分包,同时还会减小 APK 的总体大小。 #### **配置您的应用进行 Dalvik 可执行文件分包** 将您的应用项目设置为使用 Dalvik 可执行文件分包配置需要对您的应用项目进行以下修改,**具体取决于应用支持的最低 Android 版本。** **如果您的 minSdkVersion 设置为 21 或更高值,您只需在模块级 build.gradle 文件中将 multiDexEnabled 设置为 true**,如此处所示: ~~~ android { defaultConfig { ... minSdkVersion 21 targetSdkVersion 26 multiDexEnabled true } ... } ~~~ 但是,如果您的 **minSdkVersion** 设置为 20 或更低值,则您必须按如下方式使用 [Dalvik 可执行文件分包支持库](https://developer.android.com/tools/support-library/features.html#multidex): * 修改模块级 build.gradle 文件以启用 Dalvik 可执行文件分包,并将 Dalvik 可执行文件分包库添加为依赖项,如此处所示: ~~~ android { defaultConfig { ... minSdkVersion 15 targetSdkVersion 26 multiDexEnabled true } ... } dependencies { compile 'com.android.support:multidex:1.0.1' } ~~~ * 根据是否要替换 [Application](https://developer.android.com/reference/android/app/Application.html) 类,执行以下操作之一: * 如果您没有替换 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="android.support.multidex.MultiDexApplication" > ... </application> </manifest> ~~~ 如果您替换了 Application 类,请按如下方式对其进行更改以扩展 [MultiDexApplication](https://developer.android.com/reference/android/support/multidex/MultiDexApplication.html)(如果可能): ~~~ public class MyApplication extends MultiDexApplication { ... } ~~~ 或者,如果您替换了 Application 类,但无法更改基本类,则可以改为替换 [attachBaseContext()](https://developer.android.com/reference/android/content/ContextWrapper.html#attachBaseContext(android.content.Context)) 方法并调用[MultiDex.install(this)](https://developer.android.com/reference/android/support/multidex/MultiDex.html#install(android.content.Context)) 来启用 Dalvik 可执行文件分包: ~~~ public class MyApplication extends SomeOtherApplication { @Override protected void attachBaseContext(Context base) { super.attachBaseContext(context); Multidex.install(this); } } ~~~ **构建应用后,Android 构建工具会根据需要构建主 DEX 文件 (classes.dex) 和辅助 DEX 文件(classes2.dex 和 classes3.dex 等)。然后,构建系统会将所有 DEX 文件打包到您的 APK 中**。 **运行时,Dalvik 可执行文件分包 API 使用特殊的类加载器来搜索适用于您的方法的所有 DEX 文件(而不是仅在主 classes.dex 文件中搜索)**。 #### **Dalvik 可执行文件分包支持库的局限性** Dalvik 可执行文件分包支持库具有一些已知的局限性,将其纳入您的应用构建配置之中时,您应该注意这些局限性并进行针对性的测试: * 启动期间在设备数据分区中安装 DEX 文件的过程相当复杂,如果辅助 DEX 文件较大,可能会导致应用无响应 (ANR) 错误。在此情况下,您应该[通过 ProGuard 应用代码压缩](https://developer.android.com/studio/build/shrink-code.html)以尽量减小 DEX 文件的大小,并移除未使用的那部分代码。 * 由于存在 Dalvik linearAlloc 错误(问题 [22586](http://b.android.com/22586)),使用 Dalvik 可执行文件分包的应用可能无法在运行的平台版本早于 Android 4.0(API 级别 14)的设备上启动。如果您的目标 API 级别低于 14,请务必针对这些版本的平台进行测试,因为您的应用可能会在启动时或加载特定类群时出现问题。代码压缩可以减少甚至有可能消除这些潜在问题。 * 由于存在 Dalvik linearAlloc 限制(问题 [78035](http://b.android.com/78035)),因此,如果使用 Dalvik 可执行文件分包配置的应用发出非常庞大的内存分配请求,则可能会在运行期间发生崩溃。尽管 Android 4.0(API 级别 14)提高了分配限制,但在 Android 5.0(API 级别 21)之前的 Android 版本上,应用仍有可能遭遇这一限制。 #### **声明主 DEX 文件中需要的类** 为 Dalvik 可执行文件分包构建每个 DEX 文件时,构建工具会执行复杂的决策制定来确定主要 DEX 文件中需要的类,以便应用能够成功启动。如果启动期间需要的任何类未在主 DEX 文件中提供,那么您的应用将崩溃并出现错误 java.lang.NoClassDefFoundError。 该情况不应出现在直接从应用代码访问的代码上,因为构建工具能识别这些代码路径,但可能在代码路径可见性较低(如使用的库具有复杂的依赖项)时出现。例如,如果代码使用自检机制或从原生代码调用 Java 方法,那么这些类可能不会被识别为主 DEX 文件中的必需项。 因此,**如果您收到 java.lang.NoClassDefFoundError,则必须使用构建类型中的 [multiDexKeepFile](http://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.BuildType.html#com.android.build.gradle.internal.dsl.BuildType:multiDexKeepFile) 或 [multiDexKeepProguard](http://google.github.io/android-gradle-dsl/2.2/com.android.build.gradle.internal.dsl.BuildType.html#com.android.build.gradle.internal.dsl.BuildType:multiDexKeepProguard) 属性声明它们,以手动将这些其他类指定为主 DEX 文件中的必需项。如果类在 multiDexKeepFile 或 multiDexKeepProguard 文件中匹配,则该类会添加至主 DEX 文件**。 **multiDexKeepFile 属性** 您在 multiDexKeepFile 中指定的文件应该每行包含一个类,并且采用 com/example/MyClass.class 的格式。例如,您可以创建一个名为 multidex-config.txt 的文件,如下所示: ~~~ com/example/MyClass.class com/example/MyOtherClass.class ~~~ 然后,您可以按以下方式针对构建类型声明该文件: ~~~ android { buildTypes { release { multiDexKeepFile file 'multidex-config.txt' ... } } } ~~~ 请记住,Gradle 会读取相对于 build.gradle 文件的路径,因此如果 multidex-config.txt 与 build.gradle 文件在同一目录中,以上示例将有效。 **multiDexKeepProguard 属性** multiDexKeepProguard 文件使用与 Proguard 相同的格式,并且支持整个 Proguard 语法。如需了解有关 Proguard 格式和语法的详细信息,请参阅 Proguard 手册中的 [Keep Options ](http://proguard.sourceforge.net/manual/usage.html#keepoptions)一节。 您在 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 ~~~ 然后,您可以按以下方式针对构建类型声明该文件: ~~~ android { buildTypes { release { multiDexKeepProguard 'multidex-config.pro' ... } } } ~~~ #### **优化开发构建中的 Dalvik 可执行文件分包** Dalvik 可执行文件分包配置会大幅增加构建处理时间,因为构建系统必须就哪些类必须包括在主 DEX 文件中以及哪些类可以包括在辅助 DEX 文件中作出复杂的决策。这意味着使用 Dalvik 可执行文件分包的增量式构建通常耗时更长,可能会拖慢您的开发进度。 为了缩短耗时更长的 Dalvik 可执行文件分包输出构建时间,请利用 [productFlavors](http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Product-flavors)(一个开发定制和一个发布定制,具有不同的 minSdkVersion 值)创建两个构建变型。 * 对于开发定制,将 minSdkVersion 设置为 21。该设置将启用一个名为 pre-dexing 的构建功能,此功能使用仅适用于 Android 5.0(API 级别 21)和更高版本的 ART 格式更快生成 Dalvik 可执行文件分包输出。 * 对于发布定制,将 minSdkVersion 设置为适于您的实际最低支持级别。此设置生成的 Dalvik 可执行文件分包 APK 可兼容更多设备,但构建时间更长。 以下构建配置示例展示了如何在 Gradle 构建文件中设置这些定制: ~~~ android { defaultConfig { ... multiDexEnabled true } productFlavors { dev { // Enable pre-dexing to produce an APK that can be tested on // Android 5.0+ without the time-consuming DEX build processes. minSdkVersion 21 } prod { // The actual minSdkVersion for the production version. minSdkVersion 14 } } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile 'com.android.support:multidex:1.0.1' } ~~~ 您完成此配置变更后,可以为增量式构建使用应用的 devDebug 变体,后者集 dev 产品定制与 debug 构建类型的属性于一身。这将创建已启用 Dalvik 可执行文件分包且禁用 proguard 的可调试应用(因为 minifyEnabled 默认为 false)。这些设置会使适用于 Gradle 的 Android 插件执行以下操作: 1. 执行 pre-dexing:将每个应用模块和每个依赖项构建为单独的 DEX 文件。 2. 将每个 DEX 文件加入 APK,并且不做任何修改(不执行代码压缩)。 3. 最重要的是,模块 DEX 文件不执行合并操作,因此可以避免为确定主 DEX 文件的内容而进行长时间的计算。 **这些设置的好处是,可以进行快速的增量式构建,因为只有修改过的模块的 DEX 文件才会在后续构建期间重新计算并重新打包。但是,这些构建的 APK 只能用于在 Android 5.0 设备上进行测试**。不过,由于是以定制形式实现配置,您保留了使用与发布相适的最低 API 级别和 ProGuard 代码压缩执行正常构建的能力。 您还可以构建其他变体,包括 prodDebug 变体构建,该变体虽然构建时间更长,但可用于开发以外的测试。在所示配置内,prodRelease 变体将是最终测试和发布版本。如需了解有关使用构建变体的详细信息,请参阅[配置构建变体](https://developer.android.com/studio/build/build-variants.html)。 > 提示:由于您有适用于不同 Dalvik 可执行文件分包需求的不同构建变体,因此也可以为不同变体提供不同清单文件(这样,只有适用于 API 级别 20 和更低版本的清单文件会更改 `<application>` 标记名称),或者为每个变体创建不同的 Application 子类(这样,只有适用于 API 级别 20 和更低版本的清单文件会扩展 [MultiDexApplication](https://developer.android.com/reference/android/support/multidex/MultiDexApplication.html) 类或调用 [MultiDex.install(this)](https://developer.android.com/reference/android/support/multidex/MultiDex.html#install(android.content.Context)))。 #### **测试 Dalvik 可执行文件分包应用** 编写面向 Dalvik 可执行文件分包应用的仪器测试时,无需进行其他配置。[AndroidJUnitRunner](https://developer.android.com/reference/android/support/test/runner/AndroidJUnitRunner.html) 直接支持 Dalvik 可执行文件分包,前提是您使用 [MultiDexApplication](https://developer.android.com/reference/android/support/multidex/MultiDexApplication.html) 或替换您的自定义 [Application](https://developer.android.com/reference/android/app/Application.html) 对象中的 [attachBaseContext()](https://developer.android.com/reference/android/content/ContextWrapper.html#attachBaseContext(android.content.Context)) 方法,并调用 [MultiDex.install(this)](https://developer.android.com/reference/android/support/multidex/MultiDex.html#install(android.content.Context)) 以启用 Dalvik 可执行文件分包。 或者,您可以替换 [AndroidJUnitRunner](https://developer.android.com/reference/android/support/test/runner/AndroidJUnitRunner.html) 中的 onCreate() 方法: ~~~ public void onCreate(Bundle arguments) { MultiDex.install(getTargetContext()); super.onCreate(arguments); ... } ~~~ > 注:目前不支持使用 Dalvik 可执行文件分包来创建测试 APK。 #### **遗留问题**: 1. 按照官网的使用multiDexKeepFile声明主DEX 文件中需要的类,为啥按照官网的步骤,不可行呢? 2. 实际开发中可能会有更多的坑,而且官方权威的也不一定正确,一切从实际出发。