不同的Gradle多渠道配置总结2

欢迎关注微信公众号:FSA全栈行动 👋html

文章中可能涉及的到一些 groovy 语法点,可在以下文章中找到&学习:java

1、问题

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

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 库呢?api

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

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

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

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

2、探索

经过 apk 解包结果以及 gradle 配置这二者的综合考虑,显然是 defaultConfigproductFlavors 中配置的 ndk abiFilters 被合并了,为何会合并呢?又是怎么合并的?有以下两种可能性:maven

  • defaultConfigproductFlavors 修改了同一个 ndk abiFilters 配置。
  • defaultConfigproductFlavors 有各自单独的 ndk abiFilters 配置,但最终被 Android Gradle plugin 经过某种手段整合了。

一、ndk abiFilters 源码分析

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

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 集合,那么这就能够排除掉 defaultConfigproductFlavors 修改同一个 ndk abiFilters 配置的可能性。

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

二、关联 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 打包的时候须要注释掉这行依赖。

三、Set<String> abiFilters 追踪

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

提示:AS 经过 Find Usages 快捷键,能够列举出变量或方法的全部调用处,若是你的 Keymap 是 Eclipse,那么对应的快捷键是 Ctrl+G,其余 Keymap 须要本身确认。

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

根据名字,天然能够想到这个 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() 就是把 defaultConfigproductFlavors 各自的 ndk abiFilters 配置结果给整合了。

3、方案

到此为止,咱们肯定了 ndk abiFilters 的合并原理,能够理解为在 build.gradle 中, defaultConfigproductFlavors 根本就是两个不想干的配置,二者是没有办法经过某种手段直接干涉 ndk abiFilters 合并结果的。

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

那么,如今惟一的解决办法就是,defaultConfigproductFlavors 中只配一处,但是又要知足 默认只保留 'armeabi' 的前提条件,怎么办?可使用 groovy函数 + 闭包 来解决这个问题,让你见识一下什么是真正的骚操做。

一、闭包抽离

做为第三代配置脚本的 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 须要注释掉或删除。

二、闭包组合

上面运用 函数 + 闭包 这种方式很巧妙实现了 "默认配置覆盖",实现了 默认只保留 'armeabi' 这个要求,而且后期能够动态替换。但感受彷佛扩展性不强,由于原生的多渠道配置是能够很灵活的,你能够很随意的在不一样的渠道中修改各类配置,例如:buildConfigFieldapplicationId 以及其余未知的配置项。然而,咱们如今面向的不是闭包,而是一个函数,难道我每遇到一个未知的配置项,就要修改一次 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"'
}
复制代码

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

三、最终脚本配置

最后,把最终的 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
        }
    }
}
复制代码

若是文章对您有所帮助, 请不吝点击关注一下个人微信公众号:FSA全栈行动, 这将是对我最大的激励. 公众号不只有Android技术, 还有iOS, Python等文章, 可能有你想要了解的技能知识点哦~

相关文章
相关标签/搜索