【问题标题】:Using Gradle to split external libraries in separated dex files to solve Android Dalvik 64k methods limit使用Gradle将外部库拆分为单独的dex文件解决Android Dalvik 64k方法限制
【发布时间】:2014-05-12 16:21:35
【问题描述】:

有没有 proper/easy 的方式来解决使用 Gradle 的 64k 方法限制?

我的意思是一些自定义 Gradle 任务,使用预索引的 jar 来创建单独的 dex 文件,而不是单个 classes.dex

谢谢

伊万

当前状态

目前,我在 GMS 上苦苦挣扎:它引入了 20k 种方法来使用 Analytics。我使用 Proguard 去除不需要的东西,但仍然...... 72k 方法和计数......

我可以使用dx参数--multi-dexclasses.dex拆分成两个文件。我实现了手动编辑

sdk/build-tools/android-4.4W/dx

并像这样编辑最后一行:

exec java $javaOpts -jar "$jarpath" --multi-dex "$@"

我的 APK 文件现在包含 __classes.dex__ and __classes2.dex__

我正在尝试使用几种方法动态加载第二个文件:

不幸的是,仍然没有运气。我真的希望一些 Google/Facebook/Square 大师可以提供一个适当的解决方案。

【问题讨论】:

  • 请注意,如果您的问题仅出现在调试版本中,因为发布版本使用 ProGuard 会去除足够多的东西,所以还有一个方法可以为您的调试版本启用 ProGuard。
  • 我们也在调试版本中尝试了 proguard。它非常慢,并不能真正解决问题,因为我们有很多第三方库不能很好地支持 progard:guava、retrofit、rx、dagger……我们真的需要拆分 dex。
  • 这不是答案,但可能会有所帮助。在每个库中使用相同的包名(修改 AndroidManifest.xml)。如果你不这样做,那么 R.java 可能会创建很多额外的不需要的变量。这帮助我解决了一个有 64k 方法计数问题的项目。
  • @hamen 看来 sschuberth 给出了可以接受的答案,你介意让它接受吗?谢谢!
  • 特别是 GMS / GPS 遇到此问题时,另请参阅此Google Play services and DEX method limits 文章。

标签: android gradle dalvik


【解决方案1】:

Android Gradle 插件 2.2.0 更新: 无法再访问 dex 任务,但作为交换 additionalParameters 已作为 dexOptions 的一部分引入。像这样使用它

android {
  dexOptions {
    additionalParameters += '--minimal-main-dex'
    // additionalParameters += '--main-dex-list=$projectDir/<filename>'.toString()
    // additionalParameters += '--set-max-idx-number=55000'
  }
}

Android Gradle 插件 0.14.0 更新:现在通过新的 multiDexEnabled true 指令直接支持多 dex (需要 build-tools 21.1.0,支持存储库修订版 8 和 Android工作室 0.9)。

原始答案:自从 Gradle Android 插件 0.9.0 以来,您实际上可以--multi-dex 传递给 dx,方法是将其添加到您应用的 build.gradle 文件中:

afterEvaluate {
    tasks.matching {
        it.name.startsWith('dex')
    }.each { dx ->
        if (dx.additionalParameters == null) {
            dx.additionalParameters = ['--multi-dex']
        } else {
            dx.additionalParameters += '--multi-dex'
        }

        // Add more additional parameters like this:
        dx.additionalParameters += '--main-dex-list=class-list.txt'
        dx.additionalParameters += '--minimal-main-dex'
    }
}

到目前为止,正在创建多个 dex 文件。要真正使用多个 dex 文件,请查看https://github.com/casidiablo/multidex(这是 Google 即将推出的 MultiDex 支持库的一个分支)。

【讨论】:

  • 我一定会试一试的!谢谢
  • @hamen 如果它对你有用,你介意接受答案吗?
  • 我可以测试它。我不再从事那么大的项目了。对不起。请耐心等待。
  • 提示 Google 的官方解决方案。 MultiDex 支持库。这篇博文解释得很好https://medium.com/@mustafa01ali/dexs-64k-limit-is-not-a-problem-anymore-well-almost-2b1faac3508
  • 如果那篇文章的作者真的对这个 StackOverflow 线程给予了肯定,他显然是从那里复制了一些答案。
【解决方案2】:

如果 gms 是你的问题,而你正在使用 gradle

从 gms 6.5 版开始,您可以选择单独的 API 库

例如仅包含 Maps API:

compile 'com.google.android.gms:play-services-maps:6.5.87'

这是完整的列表:

      com.google.android.gms:play-services-base:6.5.87
      com.google.android.gms:play-services-ads:6.5.87
      com.google.android.gms:play-services-appindexing:6.5.87
      com.google.android.gms:play-services-maps:6.5.87
      com.google.android.gms:play-services-location:6.5.87
      com.google.android.gms:play-services-fitness:6.5.87
      com.google.android.gms:play-services-panorama:6.5.87
      com.google.android.gms:play-services-drive:6.5.87
      com.google.android.gms:play-services-games:6.5.87
      com.google.android.gms:play-services-wallet:6.5.87
      com.google.android.gms:play-services-identity:6.5.87
      com.google.android.gms:play-services-cast:6.5.87
      com.google.android.gms:play-services-plus:6.5.87
      com.google.android.gms:play-services-appstate:6.5.87
      com.google.android.gms:play-services-wearable:6.5.87
      com.google.android.gms:play-services-all-wear:6.5.87

【讨论】:

    【解决方案3】:

    可以在这里找到一个示例项目分区和加载不同的 dex 文件:

    https://code.google.com/p/android-custom-class-loading-sample/

    编辑:对于 Gradle,您已经有了答案

    Custom Class Loading in Dalvik with Gradle (Android New Build System)

    【讨论】:

    • 它使用 Ant。我想使用 Gradle。
    • 关于编辑:该解决方案似乎适用于 Java,而不是 Android。
    • @hamen:“使用 Gradle 在 Dalvik 中加载自定义类”链接指向一个关于使用 Gradle 在 Dalvik 中加载自定义类的问题和答案。
    • 你能否指点我一些文档来将 class.dex 拆分为类似:rx.dex、retrofit.dex、mockito.dex...,然后在运行时加载它们?
    【解决方案4】:

    我是https://github.com/creativepsyco/secondary-dex-gradle/的维护者,我是一个gradle n00b,所以我选择了BASH脚本的路径,虽然我认为可以直接在构建文件中完成。或者可以重构为作为插件运行,当我能够接受 Gradle 时,我可能会这样做。这是我的逻辑的原因。

    要了解如何拆分 DEX,您必须了解构建系统任务顺序。如果您使用的是 gradle,那么您必须知道在构建周期中注入了一系列任务。

    例如:

    :sdk:processReleaseJavaRes UP-TO-DATE
    :sdk:packageReleaseJar
    :sdk:compileReleaseNdk UP-TO-DATE
    :sdk:packageReleaseJniLibs UP-TO-DATE
    :sdk:packageReleaseLocalJar UP-TO-DATE
    :sdk:packageReleaseRenderscript UP-TO-DATE
    :sdk:packageReleaseResources UP-TO-DATE
    :sdk:bundleRelease
    :app:prepareComAndroidSupportAppcompatV71910Library UP-TO-DATE
    :app:prepareComFacebookAndroidFacebook3141Library UP-TO-DATE
    :app:prepareDebugDependencies
    :app:compileDebugAidl UP-TO-DATE
    :app:compileDebugRenderscript UP-TO-DATE
    :app:generateDebugBuildConfig UP-TO-DATE
    :app:generateDebugAssets UP-TO-DATE
    :app:mergeDebugAssets UP-TO-DATE
    :app:generateDebugResValues UP-TO-DATE
    :app:generateDebugResources UP-TO-DATE
    :app:mergeDebugResources UP-TO-DATE
    :app:processDebugManifest UP-TO-DATE
    :app:processDebugResources UP-TO-DATE
    :app:generateDebugSources UP-TO-DATE
    :app:compileDebugJava
    :app:preDexDebug
    :app:dexDebug
    :app:processDebugJavaRes UP-TO-DATE
    :app:validateReleaseConfigSigning
    :app:packageDebug
    :app:zipalignDebug
    :app:assembleDebug
    

    为了进行 Dexing,您应该能够在 dex* 和 process* 任务之间注入您的自定义任务。如果你能做到这一点,那么多重 DEXing 就变得容易了。

    Bash 脚本here 基本上就是这样做的,如果你检查调试任务,它基本上会:

    • 将库 Jar 文件获取到 dex,通常它是特定于构建的并且存在于 Android 库的 exploded-aar 文件夹中并在其上运行 DEX 工具
    • 将其复制到 assets 文件夹中,该文件夹位于最终要打包到应用程序中的 libs 文件夹中
    • 所有库资源等都已合并,这意味着需要再次解压缩文件。

    gradle build script

     // For Debug simply remove the library from getting dex and create it
                    //----------------------- Extra Debug Step ----------------//
                    def libraryFiles = new ArrayList<?>()
                    def secondaryFile = new ArrayList<?>()
    
                    variant.dex.libraries.each {
                        File file ->
                            if (!file.absolutePath.contains("lib/unspecified/classes.jar")) {
                                libraryFiles.add(file)
                            } else {
                                secondaryFile.add(file)
                            }
                    }
                    variant.dex.libraries = libraryFiles
                    //----------------------- Extra Debug Step ----------------//
    
                    packagingTask.dependsOn variant.javaCompile
                }
    

    这会手动将库从 dexed 中移除,以便可以通过 bash 脚本生成。

    我认为你可以用同样的方法在发布过程中找出 dexing。另一个需要注意的重要事情是 Proguard 任务是由 android gradle 插件控制的,你不能改变太多。 Proguard 规则的问题:

    • proguard 的每次传递都是不同的,我们不希望最终出现我们的 2 个 DEX 具有不同的 proguard 映射的情况
    • 这使我们处于无法保护库的情况,但这并不是真正可取的。
    • 必须在 proguard 之后生成 dex 文件以确保映射相同。 Gradle 不支持 Proguard 之后的 assets 合并(我们希望将 dex 文件放在 assets 文件夹中)

    另一个重要的代码块位于 SecondaryDex.java 中,它实质上加载了第二个 dex 文件并将 DEX 文件的路径注入运行时类路径。您可以对此进行优化,只需注入路径,而不是每次恢复应用程序时读取 DEX 文件。

    我在 Google Play Services 上做了二次 Dex 实验(增加了 20K 方法),并且能够分离成一个单独的 DEX 文件。这样我的主 dex 文件就不会受到 Google Play 服务膨胀的影响。

    要了解 Gradle 任务循环是如何工作的,您可以参考 BasePlugin.groovy 源代码,您可以看到在访问变体对象和构建任务的适当 API 之前很难控制某些方面。

    【讨论】:

    • 我相信您可以在现有 dex 任务的开头添加一个操作,为 multidex 设置内容。插入任务比较棘手。
    猜你喜欢
    • 1970-01-01
    • 2013-03-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-22
    • 1970-01-01
    • 2019-04-27
    • 2014-12-09
    相关资源
    最近更新 更多