当构建App的时候,通常都会有不同的版本。比如说测试版本,正式版本,Debug版本等等。而这些版本通常有不同的配置,比如说服务器的域名,Log开关,付费开关等等特性。
之前我们看到了Release
以及Debug
版本的概念,而接下来会介绍product flavors
的概念。而这也可以帮助我们管理不同的版本。Build Type
和Product Flavors
总是联合在一起的,它两结合的结果就称之为Build Variant
。
在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。我们需要做的就是在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会如下:
这也就意味着我们能够在同一台设备上安装多个版本的。也可以使用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中复制这些属性。它也可以重写这些属性,或者定义其他的新的属性。
当创建了一个新的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覆盖。
Build Type可以对于相同的App配置生成不同类型的构建,与Build Type相反,product flavors
用来创建相同的App,但是不同的版本。典型的例子就是App有免费和付费版本。另外一个常用就是为只有一个品牌但是有很多客户端,比如说滴滴,外卖,银行等都有司机端和用户端。他们只想修改Logo,Color,Url等等。Product Flavors
可以很简单的处理相同的代码生产出不同的版本。
如果你不确定是否需要一个新的build type,或者新的product flavor,那么则需要看一下是否真的需要构建一个新的APP发布到应用市场上。
我们可以通过添加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。
就像Build Types一样,Product Flavors能够拥有他们自己的Source Sets目录。创建一个与Product Flavors名字相同的文件夹。而这个目录的明哲,需要联合它的Build Type以及Flavors,这样用来覆盖那些属性。
比如,你想有一个不同的App Icon在blue
flavors中生成一个Release版本的包,那么这个目录应该叫做blueRelease
。然后这个组件所关联的目录将会比其他Build Type以及Product Flavors的组件目录优先级会更高。
在某些情况下,你可能希望创建一些联合的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
就会生成以下这些版本:
Build Variants仅仅只是Build Types以及Product Flavors的联合。一旦创建了一个新的Build Type或者Product Flavor的话,那么一个新的Variants就会被创建。
例如,如果有一个标准的Debug和Release构建类型,并且你创建了一个Red和Blue的Product Flavor,那么下面的Build Variant就会生成:
Build Variants
这是Android Studio中的一个窗口。可以在tool window
的左下角找到它,或者从View->Tool Windows->Build Variants
中打开。我们也可以选择其中的Variant来执行任务。如果没有定义任何的Build Types的话,Android Plugin会默认创建一个Debug的Build Type。
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。
每一个BuildType和Product Flavor都会创建新的Tasks。
Build Variants是一个联合了BuildType和ProductFlavors并且使用它们自己SourceSet目录的版本。
例如:Variant创建从Debug Build Type以及Blue、Free Flavor的版本,它可以拥有src/blueFreeDebug/java/
的Source Set。我们可以在sourceSets
代码块中重写它的location。
Android Plugin需要在打包前对Main的SourceSet以及BuildType的SourceSet进行一次Merge。而且Library工程也会提供额外的资源,它们也会被Merge,例如Manifest.xml等等。也会在其中声明一些权限等。
Resource和Manifest.xml的优先级顺序如下:
Order.png
如果一个Resource声明在Flavor和Main source set中的话,那么Flavor中的值优先级会更高。在这种情况下,Flavor的SourceSet中的资源会被打包到APK中。而Library工程的资源优先级会是最低的。
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
的样式:
blueDebug
而以下为redStaging
的样式:
redStaging
通过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。
variant filter
可以看到blueFreeRelease
和bluePaidRelease
已经不在列表中。如果直接执行gradlew tasks
的话,就会注意到所有和这个variants相关的tasks都不存在了。
在发布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。