使用Gradle将外部库拆分到独立的索引文件中,解决Android Dalvik 64k方法限制



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

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

谢谢伊万

现状

目前,我正在努力与GMS:它带来了20000种方法来使用分析。我用Proguard去掉不需要的东西,但仍然……72k方法和计数…

我可以分割classes.dex在两个文件使用dx参数——multi-dex。我手工编辑

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

和编辑最后一行:

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

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

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

  • Dexdex链接
  • Dexter链接
  • 二级索引gradle link

可惜还是没有运气。我真的希望谷歌/Facebook/Square的专家能提供一个合适的解决方案。

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指令直接支持多索引(需要build-tools 21.1.0,支持repository revision 8和Android Studio 0.9)。

原来的答案:自从Gradle Android插件0.9.0你实际上可以传递--multi-dexdx添加到你的应用程序的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'
    }
}
到目前为止,对于创建多个索引文件。要真正使用多个索引文件,请查看https://github.com/casidiablo/multidex(这是Google即将推出的MultiDex支持库的一个分支)。

如果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

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

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

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

使用Gradle在Dalvik中自定义类加载

我是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

为了执行索引,您应该能够在索引*和进程*任务之间注入自定义任务。如果你能做到这一点,那么多重数据采集就变得容易了。

这里的Bash脚本基本上是这样做的,如果您检查调试任务,它基本上会:
  • 获取库Jar文件到索引,通常它是构建特定的&存在于exploded-aar文件夹中的Android Libraries &在上面运行DEX工具
  • 复制到assets文件夹,它存在于最终的libs文件夹中,要打包到app
  • 所有的库资源等都已经合并,这意味着需要解压缩&再次压缩文件。

在gradle构建脚本

 // 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
            }

这将手动删除库的索引,以便可以通过bash脚本生成库。

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

  • 每一次的proguard都是不同的,我们不想在我们的两个dex有不同的proguard映射的情况下结束
  • 这使我们处于一种无法保护库的情况,但这并不是我们真正想要的。
  • 必须在proguard之后生成索引文件,以确保映射是相同的。Gradle不支持在Proguard之后合并资源(我们想把索引文件放在assets文件夹中)

另一个重要的代码块位于SecondaryDex.java中,它实际上加载了第二个索引文件&将DEX文件的路径注入到运行时类路径中。你可以对它进行优化,只注入路径,而不是每次恢复应用时都读取DEX文件。

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

要了解Gradle任务周期是如何工作的,你可以参考BasePlugin。在groovy源代码中,您可以看到很难控制某些方面,除非有一个适当的API来访问变量对象和构建任务。

最新更新