快捷搜索:  汽车  科技

gradle最新版本是多少(来瞧瞧新的不一样的)

gradle最新版本是多少(来瞧瞧新的不一样的)public class NdkOptions implements CoreNdkOptions Serializable { private Set<String> abiFilters; ... @NonNull public NdkOptions setAbiFilters(Collection<String> filters) { if (filters != null) { if (abiFilters == null) { abiFilters = Sets.newHashSetWithExpectedSize(filters.size()); } else { abiFilters.clea

一、问题

Android Gradle plugin 给 Android apk 打包扩展了更多的可能性,其中多渠道打包是日常开发中最为常用的配置,通过前篇文章《不一样的 Gradle 多渠道配置总结》可以了解到, Android Gradle plugin 能够让资源合并、代码整合、甚至指定各种源文件目录等等,但你是否意识到,这些功能基本上都是对原本的配置进行了扩充,而不是覆盖?而今天我就遇到了需要覆盖的情况,先来看看以下配置:

android { defaultConfig { ndk { abiFilters = ['armeabi-v7a'] } } productFlavors { xiaomi { buildConfigField "String" "CHANNEL" '"xiaomi"' } oppo { buildConfigField "String" "CHANNEL" '"oppo"' } mumu { buildConfigField "String" "CHANNEL" '"mumu"' } yeshen { ndk { abiFilters = ['x86'] } buildConfigField "String" "CHANNEL" '"yeshen"' } googleplay { ndk { abiFilters = ['armeabi' 'armeabi-v7a' 'arm64-v8a' 'x86' 'x86_64'] } buildConfigField "String" "CHANNEL" '"googleplay"' } } } dependencies { implementation 'tv.danmaku.ijk.media:ijkplayer-armv5:0.8.8' implementation 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.8.8' implementation 'tv.danmaku.ijk.media:ijkplayer-arm64:0.8.8' implementation 'tv.danmaku.ijk.media:ijkplayer-x86:0.8.8' implementation 'tv.danmaku.ijk.media:ijkplayer-x86_64:0.8.8' }

我这个配置的意图很明显,默认所有渠道的 apk 只保留 armeabi-v7a 的 so 库,而 yeshen 渠道则是只保留 x86 的 so 库,那么,以现在 yeshen 的渠道配置,打包出来的 apk 最终是否只会保留 x86 的 so 库呢?

提示:PC 上的 Android 模拟器大多数是 x86 的,为了让这类包含 so 库的 app 能正常运行,同时做到 apk 体积最小,所以需要只保留 x86 的 so 库。

当然不会,不然也不会有这篇文章了,来看 apk 解包后的截图:

gradle最新版本是多少(来瞧瞧新的不一样的)(1)

显然,这达不到我想要的预期效果,那么如何解决这个问题呢?要解决问题,得先知道问题出现的原因是什么,下面就进入探索环节。

提示:如果你赶时间,想直接知道结果,那么请拉到文末查看最终解决方案。

二、探索

通过 apk 解包结果以及 gradle 配置这两者的综合考虑,显然是 defaultConfig 与 productFlavors中配置的 ndk abiFilters 被合并了,为什么会合并呢?又是怎么合并的?有如下两种可能性:

  • defaultConfig 与 productFlavors 修改了同一个 ndk abiFilters 配置。
  • defaultConfig 与 productFlavors 有各自单独的 ndk abiFilters 配置,但最终被 Android Gradle plugin 通过某种手段整合了。
1、ndk abiFilters 源码分析

按住ctrl 点击 abiFilters,会自动跳转到 Android Gradle plugin 的 NdkOptions 的源码中:

public class NdkOptions implements CoreNdkOptions Serializable { private Set<String> abiFilters; ... @NonNull public NdkOptions setAbiFilters(Collection<String> filters) { if (filters != null) { if (abiFilters == null) { abiFilters = Sets.newHashSetWithExpectedSize(filters.size()); } else { abiFilters.clear(); } abiFilters.addAll(filters); } else { abiFilters = null; } return this; } }

也就是说 build.gradle 中 abiFilters = ['x86'] 其实调用了 NdkOptions#setAbiFilters(Collection<String> filters) 方法, 即 abiFilters = ['x86'] 等同于 setAbiFilters(['x86']);通过阅读其源码可以知道,该方法会先将 abiFilters 集合清空再添加新的 filters 集合,那么这就可以排除掉 defaultConfig 与 productFlavors 修改同一个 ndk abiFilters 配置的可能性。

提示:事实上,defaultConfig 与 productFlavors 对应的也不是同个 NdkOptions 对象。

2、关联 Android Gradle plugin 源码

在继续往下探索前,我们需要先关联 Android Gradle plugin 源码,只需要在 app 下 build.gradle 中添加如下依赖即可:

提示:为什么要关联 Android Gradle plugin 源码?因为虽然你现在可以通过 按住ctrl 点击 的方式索引到对应的源码,但你是没办法查看变量或方法在哪些地方被调用了的,这不利于代码追踪(Find Usages),阅读源码的效率就会很低下。

dependencies { // 版本号跟项目根目录build.gralde中 classpath "com.android.tools.build:gradle:3.2.0" 的版本号一致即可 api 'com.android.tools.build:gradle:3.2.0' }

这是一种很讨巧的关联源码方式,相当于把 Android Gradle plugin 当作是一个第三方库依赖进来,这样就可以像工程源码那样,进行各种源码跳转追踪了,很实用。

注意:api 'com.android.tools.build:gradle:3.2.0' 仅仅只是用于查看 Android Gradle plugin源码使用,真正在进行 apk 打包的时候需要注释掉这行依赖。

3、Set<String> abiFilters 追踪

前面已经排除了一种合并的可能,现在要验证是否会是第二种。目前我们知道了 ndk abiFilters配置肯定和 NdkOptions 源码有关,通过 Find Usages 快捷键查看 NdkOptions#setAbiFilters() 方法所有调用处,显然这个方法不是关键:

提示:AS 通过 Find Usages 快捷键,可以列举出变量或方法的所有调用处,如果你的 Keymap 是 Eclipse,那么对应的快捷键是 Ctrl G,其他 Keymap 需要自己确认。

gradle最新版本是多少(来瞧瞧新的不一样的)(2)

再通过 按住ctrl 鼠标左键 查看 Set<String> abiFilters 变量的调用处,发现了线索:

gradle最新版本是多少(来瞧瞧新的不一样的)(3)

根据名字,自然可以想到这个 MergedNdkConfig 应该就是 ndk abiFilters 配置合并的关键了:

public class MergedNdkConfig implements CoreNdkOptions { private Set<String> abiFilters; @Override @Nullable public Set<String> getAbiFilters() { return abiFilters; } public void append(@NonNull CoreNdkOptions ndkConfig) { // override if (ndkConfig.getModuleName() != null) { moduleName = ndkConfig.getModuleName(); } if (ndkConfig.getStl() != null) { stl = ndkConfig.getStl(); } if (ndkConfig.getJobs() != null) { jobs = ndkConfig.getJobs(); } // append if (ndkConfig.getAbiFilters() != null) { if (abiFilters == null) { abiFilters = Sets.newHashSetWithExpectedSize(ndkConfig.getAbiFilters().size()); } abiFilters.addAll(ndkConfig.getAbiFilters()); } if (cFlags == null) { cFlags = ndkConfig.getcFlags(); } else if (ndkConfig.getcFlags() != null && !ndkConfig.getcFlags().isEmpty()) { cFlags = cFlags " " ndkConfig.getcFlags(); } if (ndkConfig.getLdLibs() != null) { if (ldLibs == null) { ldLibs = Lists.newArrayListWithCapacity(ndkConfig.getLdLibs().size()); } ldLibs.addAll(ndkConfig.getLdLibs()); } } }

在 MergedNdkConfig#append() 方法中使用到了 NdkOptions#abiFilters 变量,再以同样的方法,可以定位到 MergedNdkConfig#append() 方法的调用处就在 GradleVariantConfiguration#mergeOptions()方法中:

public class GradleVariantConfiguration extends VariantConfiguration<CoreBuildType CoreProductFlavor CoreProductFlavor> { ... /** * Merge Gradle specific options from build types product flavors and default config. */ private void mergeOptions() { ... computeMergedOptions( mergedNdkConfig CoreProductFlavor::getNdkConfig CoreBuildType::getNdkConfig MergedNdkConfig::reset MergedNdkConfig::append); ... } private <CoreOptionT MergedOptionT> void computeMergedOptions( @NonNull MergedOptionT option @NonNull Function<CoreProductFlavor CoreOptionT> productFlavorOptionGetter @NonNull Function<CoreBuildType CoreOptionT> buildTypeOptionGetter @NonNull Consumer<MergedOptionT> reset @NonNull BiConsumer<MergedOptionT CoreOptionT> append) { reset.accept(option); CoreOptionT defaultOption = productFlavorOptionGetter.apply(getDefaultConfig()); if (defaultOption != null) { append.accept(option defaultOption); } // reverse loop for proper order final List<CoreProductFlavor> flavors = getProductFlavors(); for (int i = flavors.size() - 1 ; i >= 0 ; i--) { CoreOptionT flavorOption = productFlavorOptionGetter.apply(flavors.get(i)); if (flavorOption != null) { append.accept(option flavorOption); } } CoreOptionT buildTypeOption = buildTypeOptionGetter.apply(getBuildType()); if (buildTypeOption != null) { append.accept(option buildTypeOption); } } }

结合 GradleVariantConfiguration#computeMergedOptions() 方法,大概也能知道,其实 GradleVariantConfiguration#mergeOptions() 就是把 defaultConfig 与 productFlavors 各自的 ndk abiFilters 配置结果给整合了。

三、方案

到此为止,我们确定了 ndk abiFilters 的合并原理,可以理解为在 build.gradle 中, defaultConfig 与 productFlavors 根本就是两个不想干的配置,两者是没有办法通过某种手段直接干涉 ndk abiFilters 合并结果的。

补充:当然了,你也可以尝试继续追踪 GradleVariantConfiguration#mergeOptions() 的调用时机,看看是否能通过某个 task 来干涉 ndk abiFilters 的合并结果。反正,我目前没未找到突破点。

那么,现在唯一的解决办法就是,defaultConfig 与 productFlavors 中只配一处,可是又要满足 默认只保留 'armeabi' 的前提条件,怎么办?可以使用 groovy函数 闭包 来解决这个问题,让你见识一下什么是真正的骚操作。

1、闭包抽离

作为第三代配置脚本的 gradle 与第二代的 maven 在格式上有很大的不同,gradle 采用的是 dsl 格式,在某个配置项右边会携带一个 {},其实这一个 {} 表示的就是 groovy 语言中的一个闭包(clouser),每个配置顶可以理解为是一个接收闭包的方法,我们可以来验证一下,比如:

// 转换前 defaultConfig { applicationId "com.charylin.gradleconfigmergedemo" ... } // 转换后 defaultConfig({ applicationId "com.charylin.gradleconfigmergedemo" ... })

这两者有差吗?没有,同样能编译通过,并且能被正常识别。那么现在回过头了看看这个多渠道配置:

yeshen { ndk { abiFilters = ['x86'] } }

是不是有什么大胆的想法?没错,我们完全可以把这个闭包抽出来,改成一个返回值是闭包的函数,再结合函数可选参数指定默认值即可:

android { defaultConfig { // ndk { // abiFilters = ['armeabi-v7a'] // } } productFlavors { ... xiaomi profileLqr() oppo profileLqr() mumu profileLqr() yeshen profileLqr(['x86']) googleplay profileLqr(['armeabi' 'armeabi-v7a' 'arm64-v8a' 'x86' 'x86_64']) } } def profileLqr(abiList = null) { return { ndk { abiFilters = abiList == null ? ['armeabi-v7a'] : abiList } } }

注意:defaultConfig 中的 ndk abiFilters 需要注释掉或删除。

2、闭包组合

上面运用 函数 闭包 这种方式很巧妙实现了 "默认配置覆盖",实现了 默认只保留 'armeabi' 这个要求,并且后期可以动态替换。但感觉似乎扩展性不强,因为原生的多渠道配置是可以很灵活的,你可以很随意的在不同的渠道中修改各种配置,例如:buildConfigField、applicationId 以及其他未知的配置项。然而,我们现在面向的不是闭包,而是一个函数,难道我每遇到一个未知的配置项,就要修改一次 profileLqr() 函数吗?这显然不可取,好在 groovy 的闭包很是强大,支持闭包组合!闭包组合有 2 个常用 api:

  • Closure#rightShift:向前组合
  • Closure#leftShift:反向组合

详细使用可查看官方的 API 文档:docs.groovy-lang.org/docs/groovy…

使用闭包组合 api,渠道配置可以改写成这样:

yeshen profileLqr(['x86']).rightShift { buildConfigField "String" "CHANNEL" '"yeshen"' }

然而,rightShift 还可以进一步简写成 >>,于是,渠道配置还能改写成这样:

yeshen profileLqr(['x86']) >> { buildConfigField "String" "CHANNEL" '"yeshen"' }

就问你这操作骚不骚~ 当然了,这里对于 函数 闭包 在多渠道配置中的运用仅仅只是抛砖引玉,运动脑子,可以创建更多的可能性。

3、最终脚本配置

最后,把最终的 gradle 配置贴一下,完结,撒花:

android { defaultConfig { ... // ndk { // abiFilters = ['armeabi-v7a'] // } } productFlavors { xiaomi profileLqr() >> { buildConfigField "String" "CHANNEL" '"xiaomi"' } oppo profileLqr() >> { buildConfigField "String" "CHANNEL" '"oppo"' } mumu profileLqr() >> { buildConfigField "String" "CHANNEL" '"mumu"' } yeshen profileLqr(['x86']) >> { buildConfigField "String" "CHANNEL" '"yeshen"' } googleplay profileLqr(['armeabi' 'armeabi-v7a' 'arm64-v8a' 'x86' 'x86_64']) >> { buildConfigField "String" "CHANNEL" '"googleplay"' } } } dependencies { // 依赖源码 // api 'com.android.tools.build:gradle:3.2.0' implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support.constraint:constraint-layout:1.1.3' implementation 'tv.danmaku.ijk.media:ijkplayer-armv5:0.8.8' implementation 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.8.8' implementation 'tv.danmaku.ijk.media:ijkplayer-arm64:0.8.8' implementation 'tv.danmaku.ijk.media:ijkplayer-x86:0.8.8' implementation 'tv.danmaku.ijk.media:ijkplayer-x86_64:0.8.8' } def profileLqr(abiList = null) { return { ndk { abiFilters = abiList == null ? ['armeabi-v7a'] : abiList } } }

猜您喜欢: