Android

第四章 创建构建 Variant

2018-12-06  本文已影响0人  红酥手黄藤酒丶

第四章 创建构建 Variant

当开发一个应用时,通常会有几个不同的版本,不同版本的不同配置会让项目变得很复杂。
Gradle 中,有一些便捷和可扩展的概念可以用来定位这些常见问题。(定位!这些概念是用来描述问题的)。每个由 Android Studio 创建的新项目都会生成 debugrelease 构建类型 buildtype。另外一个概念 product flavor,其让管理多个应用或依赖版本成为可能。
buildtypeproduct flavor 结合起来一起使用,可以很容易地处理各种版本的配置问题。结合起来的产物就叫做 构建 variant

一、构建类型

GradleAndroid 插件中,构建类型通常被用来定义如何构建一个应用或依赖库。可以在 buildtypes 代码块中定义构建类型。

    buildTypes {
        release {
            //禁用清除无用的资源
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

注: 默认情况下,每个模块都有一个 debug 构建类型,其被设置为默认构建类型。debug 构建类型的 debuggable 属性为 true,其他构建类型的 debuggable 属性均为 false

1. 创建构建类型

新的构建类型,只需要在 buildTypes 代码块中新增一个对象即可:

        demo{
            // 给应用 id 添加个后缀
            applicationIdSuffix ".demo"
            // 给 版本名 添加个后缀
            versionNameSuffix "-demo"
            buildConfigField("String","API_URL","\"http://demo.xx.com/api\"")
        }

应用 id 不同,这意味着可以在同一个设备上同时安装 demo版本release版本
版本名 不同,这在同一个设备上区分多个应用版本时非常重要。

在新建一个构建类型A时,还可以用另一个构建类型B的属性来初始化构建类型A。(类似于继承)

        demo{
            initWith(buildTypes.debug)
            // 给应用 id 添加个后缀
            applicationIdSuffix ".demo"
            // 给 版本名 添加个后缀
            versionNameSuffix "-demo"
            buildConfigField("String","API_URL","\"http://demo.xx.com/api\"")
            debuggable false
        }

initWith() 方法创建了一个新的构建类型,并且复制一个已经存在的构建类型的所有属性到新的构建类型中(debug -> demo),我们在 demo 中定义了 debuggableAPI_URL 来覆盖掉 debug 的属性。

2. 源集

我们知道源集指的就是一组会被一起执行和编译的源文件。
当创建一个新的构建类型时,Gradle 也会创建一个新的源集。

3. 依赖

每个构建类型都可以有自己的依赖。Gradle 自动为每个构建类型创建新的依赖配置。如果执行在 debug 构建中添加一个 logging 框架,可以使用 debugImplementation 关键字添加依赖。

二、product flavor

与被用来配置相同 APPlibrary 的不同构建类型相反,product flavor 被用来创建不同的版本。
如果需要一个全新的 APP ,独立于已有的应用发布,那么就使用 product flavor。否则使用构建类型即可。

1. 创建 product flavor

创建 product flavor 和创建 构建类型 类似。你可以通过在 productFlavor 代码块中添加新的 product flavor 来创建:

android{
    productFlavors{
        red{
            applicationId 'com.zyf.gradlevariant.red'
            versionCode 3
            flavorDimensions("why")
        }
    
        blue{
            applicationId 'com.zyf.gradlevariant.blue'
            versionCode 4
            minSdkVersion 14
            flavorDimensions("why")
        }
    }
}

ProductFlavorDefaultConfig 均继承 BaseFlavor

image

DefaultConfig 类只有一个构造方法,没有对 BaseFlavor 做任何其他的扩展。

image

ProductFlavor 类对 BaseFlavor 做了一些扩展。

/**
 * 封装此项目的所有  产品风格(product flavor)  的属性
 *
 * <p>如果你想在同一个 设备(谷歌商店或存储库) 中同时安装你项目的不同版本,那么就使用      
 * 产品风格(product flavor) 来表示。例如,你可以给你的应用配置 ‘demo’ 和 ‘full’ 
 * 这两个共享公共源码和资源的产品风格,可以为每种产品风格指定不同的功能、设备要求、资
 * 源、应用ID。所以,产品风格允许你通过简单的输出你项目的不同版本,仅仅改变他们之间不
 * 同的组件和设置。 
 *
 * <p>配置产品风格与配置 构建类型(build-type) 相似:把 productFlavors代码块 添
 * 加到模块中的 build.gradle 文件中,然后配置你需要的内容,产品风格支持与 
 * defaultConfig 同样的属性,因为 defaultConfig 实际上也是一个 ProductFlavor 
 * 对象,defaultConfig 这个 ProductFlavor 被用来作为其他 ProductFlavor 
 * 的基础配置。可以覆盖 defaultConfig 所有默认的属性,eg:applicationId 等
 * 
 * <p>当 Android 插件版本为 3.+ 时,每个 Flavor 都必须依附于一个 dimension
 *
 * <p>配置 product flavor 时,安卓插件会自动将 product flavor 与 buildtype 
 * 中的配置相关联来创建 guild variant (构建 variant)
 * 如果插件创建了你不行要的 variant ,则可以使用 VariantFilter 进行过滤
 */
public class ProductFlavor extends BaseFlavor {

    @Inject
    public ProductFlavor(
            @NonNull String name,
            @NonNull Project project,
            @NonNull ObjectFactory objectFactory,
            @NonNull DeprecationReporter deprecationReporter,
            @NonNull Logger logger) {
        super(name, project, objectFactory, deprecationReporter, logger);
    }

    private ImmutableList<String> matchingFallbacks;

    public void setMatchingFallbacks(String... fallbacks) {
        this.matchingFallbacks = ImmutableList.copyOf(fallbacks);
    }

    public void setMatchingFallbacks(String fallback) {
        this.matchingFallbacks = ImmutableList.of(fallback);
    }

    public void setMatchingFallbacks(List<String> fallbacks) {
        this.matchingFallbacks = ImmutableList.copyOf(fallbacks);
    }

    /**
     * 当一个明确的版本不与本地的模块依赖匹配时,插件应该尝试使用基于 product 
     * flavor 的分类列表
     *
     * <p>安卓插件3.+ 会尝试使用模块中的依赖匹配你模块中的每个 构建variant 。例如
     * 当你构建一个 freeDebug 版本的app时,插件会尝试将其与本地依赖模块的 
     * freeDebug 版本匹配。For example, when you build a "freeDebug" 
     * 
     * <p>然而,也可能会有这种情况,同样的 dimension(范围;维度) 既存在于app中,
     * 也存在于 app 的依赖库中。你的 app 包含一个 依赖库 不包含的 flavor。例如,
     * 假如你的 app 和 app 的依赖库中 包含一个 “tier” 的 风格维度(flavor 
     * dimension). app 中的 "tier" 维度包含 "free" 和 "paid" (两个 
     * flavor)。 但在同样的 dimension 下其中的一个依赖项只包含 “demo” 和 
     * “paid” 这两个 flavor(风格) 。当插件尝试构建 “free” 版本时,插件并不知道
     * 要用哪个依赖版本。此时会提示错误信息:
     *
     * <pre>
     * Error:Failed to resolve: Could not resolve project :mylibrary.
     * Required by:
     *     project :app
     * </pre>
     *
     * <p>在这种情况下,你应该使用<code>matchingFallbacks</code> 来指定匹配 "free" 产品风格的代替方案,如下所示:   
     * <pre>
     * 
     * // In the app's build.gradle file.
     * 
     * android {
     *     flavorDimensions 'tier'
     *     productFlavors {
     *         paid {
     *             dimension 'tier'
     *             // 因为依赖中已经包含了一个 “paid” 风格,该风格被包含在 
     *             // “tier” 维度中(dimension) ,你不必再为 “paid” 风格提供
     *             // 特定的属性了。
     *         }
     *         free {
     *             dimension 'tier'
     *             // 当依赖匹配维度中不包含一个 “free” 风格时,此处指定回退风格
     *             // 的排序列表,你想列举多少就列举多少,插件会选择排序列表中的
     *             // 第一个风格,使用在依赖的 “tier” 维度中
     * 
     *             // 这里就是一个数组而已,如果依赖中与我们列举的 flavor 出现不
     *             // 匹配的情况,那么就默认选择此处数组的第一个作为替代方案
     *             matchingFallbacks = ['demo', 'trial']
     *         }
     *     }
     * }
     * </pre>
     *
     * <p>注意,对于应用程序及其依赖库中存在的给定 风格维度 ,当库中包含app不包含的
     *  产品风格时,不会出现问题。  
     *
     * <p>如果你正在尝试解决:依赖库包含一个风格维度但是应用程序中不包含的问题。使用
     * missingDimensionStrategy() 方法。
     * @return 返回使用的 产品风格 名称 倒序排列
     */
    public List<String> getMatchingFallbacks() {
        if (matchingFallbacks == null) {
            return ImmutableList.of();
        }
        return matchingFallbacks;
    }

    @Override
    @NonNull
    protected DimensionRequest computeRequestedAndFallBacks(@NonNull List<String> requestedValues) {
        // 这个没看懂
                VariantManager.getModifiedName(getName()), ImmutableList.copyOf(requestedValues));
    }

    @Override
    protected void _initWith(@NonNull BaseConfig that) {
        super._initWith(that);

        if (that instanceof ProductFlavor) {
            matchingFallbacks = ((ProductFlavor) that).matchingFallbacks;
        }
    }
}

2. 源集

和构建类型类似,product flavor 也可以拥有它们自己的源集目录。为一个特殊的 flavor 创建一个文件夹就和创建一个有 flavor 名称的文件一样简单。
甚至可以为一个特定构建类型(buildtype)和 flavor 的结合体创建一个文件夹。该文件夹的名称将是 flavor名称+构建类型的名称。
例如,让 blue flavorrelease 版本有一个不同的 应用图标,那么文件夹将会被叫做 blueRelease
合并文件夹的组成将比构建类型文件夹和 product flavor 文件夹拥有更高优先级。

3. 多种定制的版本

在某些情况下,我们可能需要创建 product flavor 的结合体。例如:客户 A 和客户 B 在他们的 APP 中都想要免费版和付费版,并且是基于相同的代码、不同的品牌。创建四种不同的 flavor 意味着需要像这样设置多个拷贝,这并不是最佳做法。使用 flavor dimension 是结合 flavor 的有效方式:

android{
    flavorDimensions "color","price"
    productFlavors{
        red{
            dimension "color"
        }
        blue{
            dimension "color"
        }
        free{
            dimension "price"
        }
        paid{
            dimension "price"
        }
    }
}

如果为 flavor 添加了维度,Gradle 会希望我们为每个 flavor 都添加维度。如果你忘了,那么你会收到一个带有错误接受的构建错误。 flavorDimensions 数组定义了维度,维度的顺序非常重要。当结合两个 flavor 时,它们可能定义了相同的属性或资源。在这种情况下,flavor 维度数组的顺序就决定了哪个 flavor 配置将被覆盖。在上一个例子中,color 维度覆盖了 price 维度,该顺序也决定了 构建 variant 的名称。

假设默认的构建配置是 debugrelease 构建类型(buildtype),那么上述例子中定义的 flavor 将会生成下面这些 构建 variant

三、构建variant

构建 variant构建类型product flavor 结合的结果。不论什么时候创建一个 构建类型product flavor,新的 variant 都会被创建。
例子:如果你有标注的 debugrelease 构建类型,并且你创建了一个 redblueproduct flavor ,那么会生成下图所示的 构建 variant

image
公式: flavorDimensions 中每个维度含有的 flavor 的乘积 再乘以 buildtype 的数量。
( color 含有 2 个 flavor ,price 含有 2 个 flavor ,2 * 2 = 4 。再乘以 buildtype 的数量 * 2 = 8)

在上述窗口选择 variant 后,点击

image 就会运行对应的 variant。(如果没有 product flavor,则 variant 只包含构建类型)。 image

1. 任务

GradleAndroid 会为我们配置的每一个构建 variant 创建任务。

一个新的 Android 应用默认有 debugrelease 两种构建类型,所以你可以用 assembleDebugassembleRelease 来分别构建两个 APK。即用单个命令 assemble 来创建两个 APK

当添加一个新的构建类型时,新的任务也将被创建。一旦你开始添加 flavor,那么整个全新的任务系列将会被创建,因为每个构建类型的任务会和每个 product flavor 相结合。这意味着,仅用一个构建类型和一个 flavor 做一个简单设置,你就会有三个任务用于创建全部 variant

新的 tasks 是为每个 构建类型、每个 product flavor、每个 构建类型和product flavor结合体 创建的。

2. 源集

构建variant,是一个 buildType 和 一个或多个 product flavor 的结合体,其可以有自己的 源集文件夹
例如,由 debug buildType blue flavorfree flavor 创建的 variant 可以有其自己的源集 src/blueFreeDebug/java/。其可以通过使用 sourceSet 代码块来覆盖文件夹的位置。

3. 源集合并资源和 manifest

源集的引入额外增加了构建进程的复杂性。
GradleAndroid 插件在打包应用之前将 main 源集和 buildType 源集合并在一起。此外,library 项目也可以提供额外的资源,这些也需要被合并。这同样适用于 manifest 文件。
例如:在你应用的 debug variant 中可能需要额外的 Android 权限来存储 log 文件。但你并不想在 main 源集中申请该权限,因为这样会吓跑潜在客户。相反,你可以在 debug buildType 的源集中额外添加一个 manifest 文件来申请额外的权限。
资源和 manifest 的优先顺序:

image

如果资源在 flavormain 源集中都有申明,那么 flavor 中的资源将被赋予更高的优先级,那么就会打包 flavor 中的资源。

4. variant 过滤器

在你的构建中,可以完全忽略某些 variant。通过这种方式,你就可以通过 assemble 命令来加快构建所有 variant 的进程,并且你的任务列表不会被任何无须执行的任务污染。
variant 过滤器也可确保在 Android Studio 的构建 variant 切换器中不会出现过滤的构建 variant

android{
    android.variantFilter { variant ->
        if (variant.buildType.name == 'release') {
            //说明是发布版
            variant.getFlavors().each { flavor ->
                //遍历发布版的风格
                if(flavor.name == 'blue'){
                    //说明是 蓝色发布版
                    //那么就把它给过滤了
                    variant.setIgnore(true)
                }
            }
        }
    }
}
现象: image

四、签名配置

我们在发布应用之前,会使用 私钥 给它签名。
如果你有一个付费版和免费版或针对不同用户的不同应用,那么你需要为每个 flavor 使用不同的 私钥 签名。这就是签名配置出现的原因:

android{
       //签名配置
    signingConfigs{
        demo.initWith(signingConfigs.debug)

        //构建文件中的对象都是从上到下顺序创建的
        //所以下面这个构建类型release中使用到的signingConfigs.release对象
        //要先创建,才能被使用
        //所以要将signingConfigs{}放在buildTypes{}上面
        release{
            storeFile file("release.keystore")
            storePassword "woshimima"
            keyAlias "gradleforandroid"
            keyPassword "secretpassword"
        }
    }
}

Android 插件使用了一个通用 keystore 和一个已知密码自动创建了 debug 配置,所以就没必要为该构建类型再创建一个签名配置了。
demo 配置时,使用了 initWith(),其会从另一个签名配置中复制所有属性,这意味着 demo 构建是通过 debug 密钥签名的,并不是我们自定义的。
release 配值时,使用了 storeFile 来指定 keystore 文件的路径,之后定义了密钥别名和两个密码。

住: 最好不要在 Gradle 构建文件中存储凭证,因为 Gradle 的构建文件会被上传到版本控制系统,最好的方式是使用 Gradle 配值文件。

第七章会完整介绍如何存储签名配置密码

在定义签名配置之后,需要将它们应用到 构建类型 或 flavor 中。构建类型 和 flavor 都有一个叫做 signingConfig 的属性:

android{
    buildTypes{
        release {
            signingConfig signingConfigs.release
        }
    }
}

上述例子使用了构建类型,如果你想为每个你所创建的 flavor 使用不同的凭证,那么你就需要创建不同的签名配置,也就是说给每个 flavorsigningConfig 属性赋值。

使用签名配置这种方式会造成很多问题,当为一个 flavor 分配一个配置时,实际上它是覆盖了 buildType 的签名配置。如果你不行这样,那么在使用 flavor 时,就应该为每个 buildType 的每个 flavor 分配不同的密钥,而这是可以直接在 buildType 中完成的:

android{
    //在signingConfigs中定义了两个对象:red、blue
    //注:要把signingConfigs,写在 buildTypes 前面
    signingConfigs{
        red{
        
        }
        
        blue{
        
        }
    }

    buildTypes{
         release {
            signingConfig signingConfigs.release

            //给release版本的 red flavor 配置签名 red
            productFlavors.red.signingConfig signingConfigs.red
            
            //给release版本的 blue flavor 配置签名 red
            productFlavors.blue.signingConfig signingConfigs.blue
        }
    }
}

上述例子展示了在不影响 debugdemo 构建类型的情况下,如何在 release 构建类型中为 red flavorblue flavor 使用不同的签名配置。

五、总结

构建类型是什么( buildType )、product flavor 是什么。
buildTypeproduct flavor 的结合体:构建 variant
如果需要相同代码的项目,在同一设备中安装两个应用,那么需要用到 product flavor
构建 variant 就是多渠道打包的关键概念。

签名配置是什么。

一定要注意在构建文件中每个代码块的书写顺序!

下一章:管理多模块构建。

上一篇下一篇

猜你喜欢

热点阅读