第四章 创建构建 Variant
第四章 创建构建 Variant
当开发一个应用时,通常会有几个不同的版本,不同版本的不同配置会让项目变得很复杂。
在 Gradle 中,有一些便捷和可扩展的概念可以用来定位这些常见问题。(定位!这些概念是用来描述问题的)。每个由 Android Studio 创建的新项目都会生成 debug 和 release 构建类型 buildtype
。另外一个概念 product flavor
,其让管理多个应用或依赖版本成为可能。
buildtype
和 product flavor
结合起来一起使用,可以很容易地处理各种版本的配置问题。结合起来的产物就叫做 构建 variant
。
一、构建类型
在 Gradle
的 Android
插件中,构建类型通常被用来定义如何构建一个应用或依赖库。可以在 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
中定义了 debuggable
和 API_URL
来覆盖掉 debug
的属性。
2. 源集
我们知道源集指的就是一组会被一起执行和编译的源文件。
当创建一个新的构建类型时,Gradle 也会创建一个新的源集。
3. 依赖
每个构建类型都可以有自己的依赖。Gradle 自动为每个构建类型创建新的依赖配置。如果执行在 debug 构建中添加一个 logging
框架,可以使用 debugImplementation
关键字添加依赖。
二、product flavor
与被用来配置相同 APP 或 library 的不同构建类型相反,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")
}
}
}
ProductFlavor
和 DefaultConfig
均继承 BaseFlavor
。
DefaultConfig
类只有一个构造方法,没有对 BaseFlavor
做任何其他的扩展。
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 flavor
的 release
版本有一个不同的 应用图标,那么文件夹将会被叫做 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
的名称。
假设默认的构建配置是 debug
和 release
构建类型(buildtype),那么上述例子中定义的 flavor
将会生成下面这些 构建 variant
:
- blueFreeDebug and blueFreeRelease
- bluePaidDebug and bluePaidRelease
- redFreeDebug and redFreeRelease
- redPaidDebug and redPaidRelease
三、构建variant
构建 variant
是 构建类型
和 product flavor
结合的结果。不论什么时候创建一个 构建类型
或 product flavor
,新的 variant
都会被创建。
例子:如果你有标注的 debug
和 release
构建类型,并且你创建了一个 red 和 blue 的 product flavor
,那么会生成下图所示的 构建 variant
:
公式:
flavorDimensions
中每个维度含有的 flavor
的乘积 再乘以 buildtype
的数量。( color 含有 2 个 flavor ,price 含有 2 个 flavor ,2 * 2 = 4 。再乘以 buildtype 的数量 * 2 = 8)
在上述窗口选择 variant
后,点击
variant
。(如果没有 product flavor,则 variant 只包含构建类型)。
image
1. 任务
Gradle 的 Android 会为我们配置的每一个构建 variant
创建任务。
一个新的 Android 应用默认有 debug 和 release 两种构建类型,所以你可以用 assembleDebug
和 assembleRelease
来分别构建两个 APK。即用单个命令 assemble
来创建两个 APK。
当添加一个新的构建类型时,新的任务也将被创建。一旦你开始添加 flavor
,那么整个全新的任务系列将会被创建,因为每个构建类型的任务会和每个 product flavor
相结合。这意味着,仅用一个构建类型和一个 flavor
做一个简单设置,你就会有三个任务用于创建全部 variant
:
- assembleBlue:使用
blue flavor
配置和组装BlueRelease
及BlueDebug
- image
- image
- assembleDebug:使用
debug
构建配置类型,并为每个product flavor
组装一个debug
版本- image
- image
- assembleBlueDebug:用构建配置类型(buildtype)集合
flavor
配置,并且flavor
设置将会覆盖构建类型(buildtype)设置- image
- task的名称是根据
buildVariant
窗口确定的 - image
- 所以应该输入的是:
assemble{颜色}{价格}{buildType}
,一定要按照顺序!
新的 tasks
是为每个 构建类型
、每个 product flavor
、每个 构建类型和product flavor结合体
创建的。
2. 源集
构建variant
,是一个 buildType
和 一个或多个 product flavor
的结合体,其可以有自己的 源集文件夹
。
例如,由 debug buildType
blue flavor
和 free flavor
创建的 variant
可以有其自己的源集 src/blueFreeDebug/java/
。其可以通过使用 sourceSet
代码块来覆盖文件夹的位置。
3. 源集合并资源和 manifest
源集的引入额外增加了构建进程的复杂性。
Gradle 的 Android 插件在打包应用之前将 main
源集和 buildType
源集合并在一起。此外,library
项目也可以提供额外的资源,这些也需要被合并。这同样适用于 manifest
文件。
例如:在你应用的 debug variant
中可能需要额外的 Android 权限来存储 log
文件。但你并不想在 main
源集中申请该权限,因为这样会吓跑潜在客户。相反,你可以在 debug buildType
的源集中额外添加一个 manifest
文件来申请额外的权限。
资源和 manifest
的优先顺序:
如果资源在 flavor
和 main
源集中都有申明,那么 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
使用不同的凭证,那么你就需要创建不同的签名配置,也就是说给每个 flavor
的 signingConfig
属性赋值。
使用签名配置这种方式会造成很多问题,当为一个 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
}
}
}
上述例子展示了在不影响 debug
和 demo
构建类型的情况下,如何在 release
构建类型中为 red flavor
和 blue flavor
使用不同的签名配置。
五、总结
构建类型是什么( buildType
)、product flavor
是什么。
buildType 和 product flavor 的结合体:构建 variant
。
如果需要相同代码的项目,在同一设备中安装两个应用,那么需要用到 product flavor
。
构建 variant
就是多渠道打包的关键概念。
签名配置是什么。
一定要注意在构建文件中每个代码块的书写顺序!
下一章:管理多模块构建。