介绍
当构建App的时候,通常都会有不同的版本。比如说测试版本,正式版本,Debug版本等等。而这些版本通常有不同的配置,比如说服务器的域名,Log开关,付费开关等等特性。
之前我们看到了Release
以及Debug
版本的概念,而接下来会介绍product flavors
的概念。而这也可以帮助我们管理不同的版本。Build Type
和Product Flavors
总是联合在一起的,它两结合的结果就称之为Build Variant
。
Build Types
在Gradle的Android Plugin中,Build Type
用于定义App以及Library如何构建。每一个Build Type
都会指明是否为Debug,Application Id,是否无用的资源应该被删除掉等等。你也可以在buildTypes
的代码块中定义多种Build Types。Android Studio默认生成的标准的build Types
代码块如下:
android {
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
} }
一个新的Module默认的build.gradle
文件中会配置一个release
的Build Type。这个build type通过设置minifyEnabled
为false禁用删除无用的Resources,以及定义了默认的ProGuard配置文件。
创建Project的时候不仅仅只有Release的构建类型,默认每个Module都有一个Debug的构建类型。我们可以在里面改改里面的值。
创建Build Type
当默认的配置不满足需求时,我们可以创建我们自定义的Build Type。我们需要做的就是在buildTypes
代码块中创建一个新的对象即可,如下所示,创建一个名为staging
的Build Type:
android {
buildTypes {
staging {
applicationIdSuffix ".staging"
versionNameSuffix "-staging"
buildConfigField "String", "API_URL","\"http://staging.example.com/api\""
}
}
}
staging
的Build Type定义了一些Application Id的后缀,使得Application的ID与Debug/Release版本不一样。假设你已经有了默认的Build配置,这些版本的ApplicationId会如下:
- Debug: com.package
- Release: com.package
- Staging: com.package.staging
这也就意味着我们能够在同一台设备上安装多个版本的。也可以使用buildConfigField
属性定义了不同的URL。
我们也可以通过Copy其他Build Type中的属性,来初始化一个新的BuildType,通过initWith
来初始化该BuildType对象。代码如下所示:
android {
buildTypes {
staging.initWith(buildTypes.debug)
staging {
applicationIdSuffix ".staging"
versionNameSuffix "-staging"
debuggable = false
}
}
}
initWith
方法创建了一个新的Build Type,并且从一个已经存在的build type中复制这些属性。它也可以重写这些属性,或者定义其他的新的属性。
Source sets
当创建了一个新的build type之后,Gradle也会创建一个新的source set
。默认的source set目录会放在相同的Build Type的目录下。当你创建一个新的build type时,该目录不会自动创建,你必须在你使用代码与资源前自己为每一个build type创建source set目录。
这是标准的目录结构,包含了三种Build Type:
app
└── src
├── debug
│ ├── java
│ │ └── com.package
│ ├── res
│ │ └── layout
│ │ └── activity_main.xml
│ └── AndroidManifest.xml
├── main
│ ├── java
│ │ └── com.package
│ ├── res
└── MainActivity.java
└── Constants.java
│ └── AndroidManifest.xml
├── staging
│ ├── java
│ │ └── com.package
├── drawable
└── layout
└── activity_main.xml
│ ├── res
│ │ └── layout
│ │ └── activity_main.xml
│ └── AndroidManifest.xml
└── release
├── java
│ └── com.package
│ └── Constants.java
└── AndroidManifest.xml
比如,如果希望在某个build type下替换一些属性,添加一些代码,或者添加一些layouts、strings的话,都是可以做到的。
当使用不同的source sets的时候,Resources会比较特殊。Drawables和layout文件都会被在Main Source Set中的相同名字的资源所重写,但是在values文件夹下面的,如strings、colors、dimens等则不会。Gradle会用main resources来merge各个build type的资源。
例如,如果有一个strings.xml
文件在main source set中:
<resources>
<string name="app_name">TypesAndFlavors</string>
<string name="hello_world">Hello world!</string>
</resources>
如果在staging
的build type中也存在一个strings.xml
:
<resources>
<string name="app_name">TypesAndFlavors STAGING</string>
</resources>
那么最后merge完的strings.xml会如下:
<resources>
<string name="app_name">TypesAndFlavors STAGING</string>
<string name="hello_world">Hello world!</string>
</resources>
同样AndroidManifest.xml也是同样的,特定的build type的包会把main source set中的AndroidManifest.xml覆盖。
Product flavors
Build Type可以对于相同的App配置生成不同类型的构建,与Build Type相反,product flavors
用来创建相同的App,但是不同的版本。典型的例子就是App有免费和付费版本。另外一个常用就是为只有一个品牌但是有很多客户端,比如说滴滴,外卖,银行等都有司机端和用户端。他们只想修改Logo,Color,Url等等。Product Flavors
可以很简单的处理相同的代码生产出不同的版本。
如果你不确定是否需要一个新的build type,或者新的product flavor,那么则需要看一下是否真的需要构建一个新的APP发布到应用市场上。
创建Product Flavors
我们可以通过添加productFlavor
代码块来添加一个新的Product Flavor:
android {
productFlavors {
red {
applicationId 'com.gradleforandroid.red'
versionCode 3
}
blue {
applicationId 'com.gradleforandroid.blue'
minSdkVersion 14
versionCode 4
}
}
}
Product Flavors
拥有和Build Type
不同的属性。因为Product Flavors
是一个ProductFlavor
类,就像defaultConfig
对象一样。这也就意味着,defaultConfig
和所有的Product flavors
共享相同的Properties。
Source Set
就像Build Types一样,Product Flavors能够拥有他们自己的Source Sets目录。创建一个与Product Flavors名字相同的文件夹。而这个目录的明哲,需要联合它的Build Type以及Flavors,这样用来覆盖那些属性。
比如,你想有一个不同的App Icon在blue
flavors中生成一个Release版本的包,那么这个目录应该叫做blueRelease
。然后这个组件所关联的目录将会比其他Build Type以及Product Flavors的组件目录优先级会更高。
Multiflavor variants
在某些情况下,你可能希望创建一些联合的Product Flavors。比如说,Client A和Client B都基于相同的代码需要一个免费和付费的版本。创建四个不同的Flavors单独的Settings是不可行的。所以,Combining Flavors可以更高效的使用flavor dimensions
:
android {
flavorDimensions "color", "price"
productFlavors {
red {
flavorDimension "color"
}
blue {
flavorDimension "color"
}
free {
flavorDimension "price"
}
paid {
flavorDimension "price"
}
}
}
当添加了flavor dimensions
之后,Gradle希望你为每个Flavor都指定一个flavor dimension
。如果你忘记了,则编译时会报错。flavorDimensions
数组定义了这些Dimensions,而这些Dimensions的顺序是非常重要的。当需要联合两个Flavors的时候,你可能已经定义了相同的Properties或者Resources。在这种情况下,flavors dimensions
数组的顺序决定了哪个flavor
配置会覆盖另外的。在之前的例子中,Color Dimension
会覆盖Price Dimension
。并且这个顺序,也决定了构建的名字。
假设默认的构建配置有Debug和Release两种Build Type,就像之前的Example中定义的flavors
就会生成以下这些版本:
- blueFreeDebug and blueFreeRelease
- bluePaidDebug and bluePaidRelease
- redFreeDebug and redFreeRelease
- redPaidDebug and redPaidRelease
Build variants
Build Variants仅仅只是Build Types以及Product Flavors的联合。一旦创建了一个新的Build Type或者Product Flavor的话,那么一个新的Variants就会被创建。
例如,如果有一个标准的Debug和Release构建类型,并且你创建了一个Red和Blue的Product Flavor,那么下面的Build Variant就会生成:
这是Android Studio中的一个窗口。可以在tool window
的左下角找到它,或者从View->Tool Windows->Build Variants
中打开。我们也可以选择其中的Variant来执行任务。如果没有定义任何的Build Types的话,Android Plugin会默认创建一个Debug的Build Type。
Tasks
Android Plugin会为每一个配置的Build Variant创建Tasks。一个新的Android App拥有Debug和Release两种Build Types,所以默认的就会有两个Task,一个是assembleDebug
一个是assembleRelease
来构建不同的APK。当添加一个新的Build Type的时候,一个新的Task也就会被创建,一旦你开始添加Flavors,一整套Tasks就会被创建,因为每一个BuildType的Tasks都会为每个Product Flavor联合。也就是,一个简单的Build Type和Flavor设置后,就会有三个任务去构建所有的Variants。
- assembleBlue:使用blue flavor配置并且assemble BlueRelease和BlueDebug
- assembleDebug:使用Debug Build Type的配置,并且为每一个Product Flavor assemble一个Debug的版本
- assembleBlueDebug:combines特定的Flavor以及BuildType配置,并且Flavor的设置会覆盖BuildType的设置
每一个BuildType和Product Flavor都会创建新的Tasks。
Source Set
Build Variants是一个联合了BuildType和ProductFlavors并且使用它们自己SourceSet目录的版本。
例如:Variant创建从Debug Build Type以及Blue、Free Flavor的版本,它可以拥有src/blueFreeDebug/java/
的Source Set。我们可以在sourceSets
代码块中重写它的location。
Resource and manifest merging
Android Plugin需要在打包前对Main的SourceSet以及BuildType的SourceSet进行一次Merge。而且Library工程也会提供额外的资源,它们也会被Merge,例如Manifest.xml等等。也会在其中声明一些权限等。
Resource和Manifest.xml的优先级顺序如下:
如果一个Resource声明在Flavor和Main source set中的话,那么Flavor中的值优先级会更高。在这种情况下,Flavor的SourceSet中的资源会被打包到APK中。而Library工程的资源优先级会是最低的。
Creating build variants
Gradle可以很容易的处理复杂的多种构建。甚至当创建两种BuildType和两种Product Flavors的时候。例如:
android {
buildTypes {
debug {
buildConfigField "String", "API_URL","\"http://test.example.com/api\""
}
staging.initWith(android.buildTypes.debug)
staging {
buildConfigField "String", "API_URL","\"http://staging.example.com/api\""
applicationIdSuffix ".staging"
}
}
productFlavors {
red {
applicationId "com.gradleforandroid.red"
resValue "color", "flavor_color", "#ff0000"
}
blue {
applicationId "com.gradleforandroid.blue"
resValue "color", "flavor_color", "#0000ff"
}
}
}
在这个例子中,我们会创建出来四个不同版本的Variants:
blueDebug
,blueStaging
,redDebug
,redStaging
每一个都有API_URL
以及flavor_color
的属性。
以下为blueDebug
的样式:
而以下为redStaging
的样式:
Variant filters
通过Variant fileters的方式,可以完全忽略某种Variant的构建,从而达到使用assemble
命令的时候提升构建的速度。并且不会执行的Task也不会打印的Tasks列表中出现。这样也同样会确保build variant不会在Android Studio中显示。
我们可以通过在App或者Library的Root-Level的build.gradle
文件中添加以下代码:
android.variantFilter { variant ->
if(variant.buildType.name.equals('release')) {
variant.getFlavors().each() { flavor ->
if (flavor.name.equals('blue')) {
variant.setIgnore(true);
}
}
}
}
在这个例子中,首先检查BuildType是否为Release,然后检查Flavors的名字,如果flavors为blue
则忽略。其中variant.getFlovors
会获取到flavor dimensions
中所有的flavor。
可以看到blueFreeRelease
和bluePaidRelease
已经不在列表中。如果直接执行gradlew tasks
的话,就会注意到所有和这个variants相关的tasks都不存在了。
Signing configurations
在发布App到Google Play或者其他的商店的时候,我们需要使用一个Private Key对APK进行签名。如果有一个付费和免费的版本,或者不同的客户端版本时,你需要为不同的Flavor版本APK进行不同的签名。
android {
signingConfigs {
staging.initWith(signingConfigs.debug)
release {
storeFile file("release.keystore")
storePassword"secretpassword"
keyAlias "gradleforandroid"
keyPassword "secretpassword"
}
}
}
在这个例子中,我们创建了两个不同的签名。
debug
配置会被Android Plugin自动设置,并且使用一个已知的Password进行签名,所以不需要为Debug的BuildType创建签名配置。而staging
配置使用initWith
,它是从另外一个签名配置中Copy的属性。这也就意味着staging
的构建会和Debug一样的签名,而没有它自己定义的签名。
而release
配置则使用storeFile
来指定keystore
文件,并且定义了Key的别名以及Password。
当定义完了这个签名的配置后,你需要在BuildType或者Flavors中应用一下。BuildType和Flavors都有一个属性叫做signingConfig
,如下所示:
android {
buildTypes {
release {
signingConfig signingConfigs.release
}
}
productFlavors {
blue {
signingConfig signingConfigs.release
}
}
}
通过这种方式会对BuildType以及ProductFlavors应用不同的签名。
当签名一个Flavor版本的时候,你需要重写BuildType中的签名配置W。当需要使用相同的BuildType不同版本的Flavors的签名时,可以通过下述方式:
android {
buildTypes {
release {
productFlavors.red.signingConfig signingConfigs.red
productFlavors.blue.signingConfig signingConfigs.blue
}
}
}
上面这个例子展示了如何在red
和blue
的Release版本使用不同的签名,但是却不影响Debug和Staging的BuildType。