介紹
當(dāng)構(gòu)建App的時候,通常都會有不同的版本。比如說測試版本,正式版本,Debug版本等等。而這些版本通常有不同的配置,比如說服務(wù)器的域名,Log開關(guān),付費(fèi)開關(guān)等等特性。
之前我們看到了Release以及Debug版本的概念,而接下來會介紹product flavors的概念。而這也可以幫助我們管理不同的版本。Build Type和Product Flavors總是聯(lián)合在一起的,它兩結(jié)合的結(jié)果就稱之為Build Variant。
Build Types
在Gradle的Android Plugin中,Build Type用于定義App以及Library如何構(gòu)建。每一個Build Type都會指明是否為Debug,Application Id,是否無用的資源應(yīng)該被刪除掉等等。你也可以在buildTypes的代碼塊中定義多種Build Types。Android Studio默認(rèn)生成的標(biāo)準(zhǔn)的build Types代碼塊如下:
android {
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
} }
一個新的Module默認(rèn)的build.gradle文件中會配置一個release的Build Type。這個build type通過設(shè)置minifyEnabled為false禁用刪除無用的Resources,以及定義了默認(rèn)的ProGuard配置文件。
創(chuàng)建Project的時候不僅僅只有Release的構(gòu)建類型,默認(rèn)每個Module都有一個Debug的構(gòu)建類型。我們可以在里面改改里面的值。
創(chuàng)建Build Type
當(dāng)默認(rèn)的配置不滿足需求時,我們可以創(chuàng)建我們自定義的Build Type。我們需要做的就是在buildTypes代碼塊中創(chuàng)建一個新的對象即可,如下所示,創(chuàng)建一個名為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版本不一樣。假設(shè)你已經(jīng)有了默認(rèn)的Build配置,這些版本的ApplicationId會如下:
- Debug: com.package
- Release: com.package
- Staging: com.package.staging
這也就意味著我們能夠在同一臺設(shè)備上安裝多個版本的。也可以使用buildConfigField屬性定義了不同的URL。
我們也可以通過Copy其他Build Type中的屬性,來初始化一個新的BuildType,通過initWith來初始化該BuildType對象。代碼如下所示:
android {
buildTypes {
staging.initWith(buildTypes.debug)
staging {
applicationIdSuffix ".staging"
versionNameSuffix "-staging"
debuggable = false
}
}
}
initWith方法創(chuàng)建了一個新的Build Type,并且從一個已經(jīng)存在的build type中復(fù)制這些屬性。它也可以重寫這些屬性,或者定義其他的新的屬性。
Source sets
當(dāng)創(chuàng)建了一個新的build type之后,Gradle也會創(chuàng)建一個新的source set。默認(rèn)的source set目錄會放在相同的Build Type的目錄下。當(dāng)你創(chuàng)建一個新的build type時,該目錄不會自動創(chuàng)建,你必須在你使用代碼與資源前自己為每一個build type創(chuàng)建source set目錄。
這是標(biāo)準(zhǔn)的目錄結(jié)構(gòu),包含了三種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的話,都是可以做到的。
當(dāng)使用不同的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配置生成不同類型的構(gòu)建,與Build Type相反,product flavors用來創(chuàng)建相同的App,但是不同的版本。典型的例子就是App有免費(fèi)和付費(fèi)版本。另外一個常用就是為只有一個品牌但是有很多客戶端,比如說滴滴,外賣,銀行等都有司機(jī)端和用戶端。他們只想修改Logo,Color,Url等等。Product Flavors可以很簡單的處理相同的代碼生產(chǎn)出不同的版本。
如果你不確定是否需要一個新的build type,或者新的product flavor,那么則需要看一下是否真的需要構(gòu)建一個新的APP發(fā)布到應(yīng)用市場上。
創(chuàng)建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目錄。創(chuàng)建一個與Product Flavors名字相同的文件夾。而這個目錄的明哲,需要聯(lián)合它的Build Type以及Flavors,這樣用來覆蓋那些屬性。
比如,你想有一個不同的App Icon在blue flavors中生成一個Release版本的包,那么這個目錄應(yīng)該叫做blueRelease。然后這個組件所關(guān)聯(lián)的目錄將會比其他Build Type以及Product Flavors的組件目錄優(yōu)先級會更高。
Multiflavor variants
在某些情況下,你可能希望創(chuàng)建一些聯(lián)合的Product Flavors。比如說,Client A和Client B都基于相同的代碼需要一個免費(fèi)和付費(fèi)的版本。創(chuàng)建四個不同的Flavors單獨(dú)的Settings是不可行的。所以,Combining Flavors可以更高效的使用flavor dimensions:
android {
flavorDimensions "color", "price"
productFlavors {
red {
flavorDimension "color"
}
blue {
flavorDimension "color"
}
free {
flavorDimension "price"
}
paid {
flavorDimension "price"
}
}
}
當(dāng)添加了flavor dimensions之后,Gradle希望你為每個Flavor都指定一個flavor dimension。如果你忘記了,則編譯時會報錯。flavorDimensions數(shù)組定義了這些Dimensions,而這些Dimensions的順序是非常重要的。當(dāng)需要聯(lián)合兩個Flavors的時候,你可能已經(jīng)定義了相同的Properties或者Resources。在這種情況下,flavors dimensions數(shù)組的順序決定了哪個flavor配置會覆蓋另外的。在之前的例子中,Color Dimension會覆蓋Price Dimension 。并且這個順序,也決定了構(gòu)建的名字。
假設(shè)默認(rèn)的構(gòu)建配置有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的聯(lián)合。一旦創(chuàng)建了一個新的Build Type或者Product Flavor的話,那么一個新的Variants就會被創(chuàng)建。
例如,如果有一個標(biāo)準(zhǔn)的Debug和Release構(gòu)建類型,并且你創(chuàng)建了一個Red和Blue的Product Flavor,那么下面的Build Variant就會生成:

這是Android Studio中的一個窗口。可以在tool window的左下角找到它,或者從View->Tool Windows->Build Variants中打開。我們也可以選擇其中的Variant來執(zhí)行任務(wù)。如果沒有定義任何的Build Types的話,Android Plugin會默認(rèn)創(chuàng)建一個Debug的Build Type。
Tasks
Android Plugin會為每一個配置的Build Variant創(chuàng)建Tasks。一個新的Android App擁有Debug和Release兩種Build Types,所以默認(rèn)的就會有兩個Task,一個是assembleDebug一個是assembleRelease來構(gòu)建不同的APK。當(dāng)添加一個新的Build Type的時候,一個新的Task也就會被創(chuàng)建,一旦你開始添加Flavors,一整套Tasks就會被創(chuàng)建,因為每一個BuildType的Tasks都會為每個Product Flavor聯(lián)合。也就是,一個簡單的Build Type和Flavor設(shè)置后,就會有三個任務(wù)去構(gòu)建所有的Variants。
- assembleBlue:使用blue flavor配置并且assemble BlueRelease和BlueDebug
- assembleDebug:使用Debug Build Type的配置,并且為每一個Product Flavor assemble一個Debug的版本
- assembleBlueDebug:combines特定的Flavor以及BuildType配置,并且Flavor的設(shè)置會覆蓋BuildType的設(shè)置
每一個BuildType和Product Flavor都會創(chuàng)建新的Tasks。
Source Set
Build Variants是一個聯(lián)合了BuildType和ProductFlavors并且使用它們自己SourceSet目錄的版本。
例如:Variant創(chuàng)建從Debug Build Type以及Blue、Free Flavor的版本,它可以擁有src/blueFreeDebug/java/的Source Set。我們可以在sourceSets代碼塊中重寫它的location。
Resource and manifest merging
Android Plugin需要在打包前對Main的SourceSet以及BuildType的SourceSet進(jìn)行一次Merge。而且Library工程也會提供額外的資源,它們也會被Merge,例如Manifest.xml等等。也會在其中聲明一些權(quán)限等。
Resource和Manifest.xml的優(yōu)先級順序如下:

如果一個Resource聲明在Flavor和Main source set中的話,那么Flavor中的值優(yōu)先級會更高。在這種情況下,F(xiàn)lavor的SourceSet中的資源會被打包到APK中。而Library工程的資源優(yōu)先級會是最低的。
Creating build variants
Gradle可以很容易的處理復(fù)雜的多種構(gòu)建。甚至當(dāng)創(chuàng)建兩種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"
}
}
}
在這個例子中,我們會創(chuàng)建出來四個不同版本的Variants:
blueDebug,blueStaging,redDebug,redStaging
每一個都有API_URL以及flavor_color的屬性。
以下為blueDebug的樣式:

而以下為redStaging的樣式:

Variant filters
通過Variant fileters的方式,可以完全忽略某種Variant的構(gòu)建,從而達(dá)到使用assemble命令的時候提升構(gòu)建的速度。并且不會執(zhí)行的Task也不會打印的Tasks列表中出現(xiàn)。這樣也同樣會確保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已經(jīng)不在列表中。如果直接執(zhí)行gradlew tasks的話,就會注意到所有和這個variants相關(guān)的tasks都不存在了。
Signing configurations
在發(fā)布App到Google Play或者其他的商店的時候,我們需要使用一個Private Key對APK進(jìn)行簽名。如果有一個付費(fèi)和免費(fèi)的版本,或者不同的客戶端版本時,你需要為不同的Flavor版本APK進(jìn)行不同的簽名。
android {
signingConfigs {
staging.initWith(signingConfigs.debug)
release {
storeFile file("release.keystore")
storePassword"secretpassword"
keyAlias "gradleforandroid"
keyPassword "secretpassword"
}
}
}
在這個例子中,我們創(chuàng)建了兩個不同的簽名。
debug配置會被Android Plugin自動設(shè)置,并且使用一個已知的Password進(jìn)行簽名,所以不需要為Debug的BuildType創(chuàng)建簽名配置。而staging配置使用initWith,它是從另外一個簽名配置中Copy的屬性。這也就意味著staging的構(gòu)建會和Debug一樣的簽名,而沒有它自己定義的簽名。
而release配置則使用storeFile來指定keystore文件,并且定義了Key的別名以及Password。
當(dāng)定義完了這個簽名的配置后,你需要在BuildType或者Flavors中應(yīng)用一下。BuildType和Flavors都有一個屬性叫做signingConfig,如下所示:
android {
buildTypes {
release {
signingConfig signingConfigs.release
}
}
productFlavors {
blue {
signingConfig signingConfigs.release
}
}
}
通過這種方式會對BuildType以及ProductFlavors應(yīng)用不同的簽名。
當(dāng)簽名一個Flavor版本的時候,你需要重寫B(tài)uildType中的簽名配置W。當(dāng)需要使用相同的BuildType不同版本的Flavors的簽名時,可以通過下述方式:
android {
buildTypes {
release {
productFlavors.red.signingConfig signingConfigs.red
productFlavors.blue.signingConfig signingConfigs.blue
}
}
}
上面這個例子展示了如何在red和blue的Release版本使用不同的簽名,但是卻不影響Debug和Staging的BuildType。