第四章 創建構建 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 只包含構建類型)。
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)
}
}
}
}
}
現象:四、簽名配置
我們在發布應用之前,會使用 私鑰
給它簽名。
如果你有一個付費版和免費版或針對不同用戶的不同應用,那么你需要為每個 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
就是多渠道打包的關鍵概念。
簽名配置是什么。
一定要注意在構建文件中每個代碼塊的書寫順序!
下一章:管理多模塊構建。