--main-dex-list 中太多 类,主 dex 容量超出

Too many classes in --main-dex-list, main dex capacity exceeded

我正在尝试 运行 仪器测试用例,但在 dex 转换时出现以下错误 意外的顶级异常:

com.android.dex.DexException: Too many classes in --main-dex-list, main dex capacity exceeded
        at com.android.dx.command.dexer.Main.processAllFiles(Main.java:494)
        at com.android.dx.command.dexer.Main.runMultiDex(Main.java:334)
        at com.android.dx.command.dexer.Main.run(Main.java:244)
        at com.android.dx.command.dexer.Main.main(Main.java:215)
        at com.android.dx.command.Main.main(Main.java:106)

:App:dexDebug FAILED

如何解决 gradle 中的这个问题?

你有两个选择。

  1. 使用 ProGuard 减少方法数量
  2. 使用 multidex 功能

我的建议 - 使用 ProGuard,它只需对源代码进行零改动

我们先来了解一下问题:

在 Lollipop 之前的设备上,框架仅加载主 dex。要支持多 dex 应用程序,您必须使用所有辅助 dex 文件显式修补应用程序 class 加载器(这就是为什么您的应用程序 class 必须扩展 MultiDexApplication class or call MultiDex#install)。

这意味着您的应用程序的主 dex 应该包含所有 class 在 class 加载程序修补之前可能访问的 es。

如果您的应用程序代码在成功修补应用程序 class 加载程序之前尝试引用打包在您的一个辅助 dex 文件中的 class,您将收到 java.lang.ClassNotFoundException

我已经 documented here 插件如何决定哪些 class 应该打包到 main-dex 中。
如果这些 classes 引用的方法总数超过 65,536 个限制,则构建将失败并出现 Too many classes in --main-dex-list, main dex capacity exceeded 错误。

对于这个问题,我可以想到三种可能的解决方案:

  1. (最简单的解决方案,但不适合大多数人 应用程序) 将您的 minSdkVersion 更改为 21。
  2. 压缩您的应用程序代码。这在之前已经讨论过很多次(参见 here and here)。
  3. 如果上述 none 解决方案适合您,您可以尝试使用 my workaround 解决此问题 - 我正在修补 Android gradle 插件以不在主索引中包含 Activity classes。这有点hacky,但对我来说效果很好。

Android 错误跟踪器中有 an issue 关于此错误。希望工具团队尽快提供更好的解决方案。


更新 (4/27/2016)

2.1.0 版 Gradle 插件允许过滤 main-dex 列表 classes.
警告:这正在使用不受支持的 api,将来会被替换。

例如,要排除所有 activity classes 你可以这样做:

afterEvaluate {
  project.tasks.each { task ->
    if (task.name.startsWith('collect') && task.name.endsWith('MultiDexComponents')) {
      println "main-dex-filter: found task $task.name"
      task.filter { name, attrs ->
        def componentName = attrs.get('android:name')
        if ('activity'.equals(name)) {
          println "main-dex-filter: skipping, detected activity [$componentName]"
          return false
        } else {
          println "main-dex-filter: keeping, detected $name [$componentName]"
          return true
        }
      }
    }
  }
}

您还可以查看我的 example project 演示此问题(并应用上述过滤)。


更新 2 (7/1/2016)

Gradle 插件的 2.2.0-alpha4 版本(带有 build-tools v24)最终通过 reducing multidex keep list to a minimum.
解决了这个问题 不应再使用 2.1.0 中不受支持(且未记录)的过滤器。我已经更新了我的 sample project,证明构建现在成功了,没有任何自定义构建逻辑。

解决此问题的另一种方法是从主 DEX 文件中删除带有运行时注释的 类:

android {

    dexOptions {
        keepRuntimeAnnotatedClasses false
    }

}

这对于使用依赖注入框架的应用程序特别有用,因为即使是 Dagger 注释通常也保留在运行时。