Gradle for Android(四) 創建構建變體

開發應用時, 通常會有幾個不同的版本。最常見的是有一個測試用的臨時版本和一個生產版本。這些版本通常有不同的設置,比如不同的URL地址等。除此之外,你可能有一個免費版和一個包含更多功能的付費版。這種情況下,你已經有了四個不同的版本:臨時免費版,臨時付費版,生產免費版,生產付費版。不同的版本有不同的配置是非常復雜的事情。

Gradle有一些規則和可擴展的概念來解決這一問題。我們已經提及Android Studio為每個新建工程創建的debugrelease兩種build types。這是另一個稱為product flavors的概念,它可以管理一個app或者library的不同版本。build typesproduct flavors是結合起來使用的,這就使管理免費和付費版的臨時和生產應用變得簡單。build typeproduct flavor的結合稱為build variant(構建變體)。

本章我們首先學習build types,看看它如何使開發者的工作更加簡單。接下來我們會討論build typesproduct flavors的不同以及如何使用它們。我們還會學習發布app所必需的signing configurations(簽名配置),以及如何為每個構建變體設置不同的簽名配置。

本章我們學習如下內容:

  • build types
  • product flavors
  • build variants
  • signing configurations

Build types(構建類型)

在Gradle Android插件中,構建類型用來定義一個app或者library應該如何構建。每個構建類型可以指定是否包含調試符號,applicationId是什么,無用的資源是否應該移除等等。你可以使用buildTypes塊來定義構建類型。下面是Android Studio創建的標準buildTypes塊:

android {
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

新模塊默認的build.gradle文件配置了一個名為release的構建類型。它只是禁用了移除無用的資源(通過設置minifyEnabledfalse),并且定義了ProGuard配置文件的位置。這是為了讓開發人員可以直接使用ProGuard進行生產構建。

release構建類型不是唯一一個自動為你的工程創建好的構建類型。每個模塊默認還有一個debug構建類型。它設置有合理的默認值,你也可以在buildTypes塊中配置它,覆寫屬性值。

debug構建類型擁有它自己的默認設置,以使它方便調試。當你創建自己的構建類型時,會有不同的默認值。比如,debug構建類型的debuggable屬性設置為true,而你自己創建的其他類型為false

創建構建類型

當默認設置不滿足需求時,你也可以很容易地創建自定義構建類型。你需要做的只是在buildTypes塊中添加一個新的構建類型的對象。下面創建一個staging的構建類型:

android {
    buildTypes {
        staging {
            applicationIdSuffix ".staging"
            versionNameSuffix "-staging"
            buildConfigField "String", "API_URL","\"http://staging.example.com/api\""
        }
    }
}

staging構建類型定義了一個新的applicationId的后綴,這樣就和debugrelease版本區分開來。假設其他配置使用默認值,這樣每個構建類型的applicationId如下:

  • Debug:com.package
  • Release:com.package
  • Staging:com.package.staging

這意味著你可以在設備上同時安裝release版本和staging版本,而不會引起沖突。staging構建類型也有一個版本名稱的后綴,用于區分同一設備不同版本的app。buildConfigField屬性定義了一個URL地址,我們在第二章已經見過。

在創建構建類型時,你不必完全自定義,你也可以從由其他構建類型來初始化。

android {
    buildTypes {
        staging.initWith(buildTypes.debug)
        staging {
            applicationIdSuffix ".staging"
            versionNameSuffix "-staging"
            debuggable = false
        }
    }
}

initWith()方法會根據傳入的構建類型來初始化當前構建類型。你可以在新的構建類型對象中覆寫屬性,或者定義新的屬性。

Source sets(源碼集)

在你創建一個新構建類型時,Gradle也會創建一個新的源碼集。源碼集目錄默認和構建類型同名,但并不會自動創建。你需要手動創建該目錄才能為該構建類型自定義源代碼和資源文件。

下面是標準的debugrelease構建類型,外加自定義的staging構建類型的源碼集目錄結構:

app
└── src
    ├── debug
    │    ├── java
    │    │   └── com.package
    │    │         └── Constants.java
    │    ├── res
    │    │  └── layout
    │    │         └── activity_main.xml
    │    └── AndroidManifest.xml
    ├── main
    │    ├── java
    │    │    └── com.package
    │    │            └── MainActivity.java
    │    ├── res
    │    │    ├── drawable
    │    │    └── layout
    │    │            └── activity_main.xml
    │    └── AndroidManifest.xml
    ├── staging
    │    ├── java
    │    │    └── com.package
    │    │            └── Constants.java
    │    ├── res
    │    │    └── layout
    │    │            └── activity_main.xml
    │    └── AndroidManifest.xml
    └── release
         ├── java
         │     └── com.package
         │          └── Constants.java
         └── AndroidManifest.xml

這些源碼集帶來巨大的便利,比如你可以為某個構建類型覆蓋特定屬性,添加特定的代碼,添加特殊的布局文件或者字符串資源等。

在你為每個構建類型添加Java類的時候,需要知道這是互斥的。這意味著如果你在staging的源碼集中添加了CustomLogic.java文件,你可以在debug或者release源碼集中添加同一個文件,但是不可以在main中添加,否則該文件會被定義兩次,從而在構建時引發異常。

資源文件的處理方式和代碼文件不同。圖片和布局文件會覆蓋main中的同名文件,但是values目錄下的文件(比如strings.xml)不會。Gradle會將該構建類型和main中的values目錄下的文件進行合并。

舉個例子,假如main中的strings.xml文件如下:

<resources>
    <string name="app_name">TypesAndFlavors</string>
    <string name="hello_world">Hello world!</string>
</resources>

staging中的strings.xml如下:

<resources>
    <string name="app_name">TypesAndFlavors STAGING</string>
</resources>

合并后的strings.xml文件為:

<resources>
    <string name="app_name">TypesAndFlavors STAGING</string>
    <string name="hello_world">Hello world!</string>
</resources>

以上規則同樣適用于manifest文件。當你為一個構建類型創建manifest文件時,你不必從main中將整個manifest文件拷貝過來,你只需要添加你需要的標簽就可以了。Android插件會將它們合并到一起。

本章的后續內容我們會詳細討論文件合并。

Product flavors

構建類型用來為同一個app或者library配置不同的構建,與之相反,product flavors用來為同一個app創建不同的版本。典型的例子是一個app有一個免費版本和一個付費版本。另一個常見的場景是一個代理程序,它為幾個客戶構建具有相同功能的應用程序,只是品牌發生改變。一個公司制作的應用可以被同類型的客戶重復使用,這在出租車行業和銀行領域是很常見的。改變的僅僅是顏色,logo,后臺地址。Product flavors極大簡化了創建基于同樣代碼的不同版本的應用的過程。

如果你不確定自己需要新的build type還是新的product flavor,你應該問問自己是想要為了內部使用為應用創建一個新的構建,還是想要向Google Play發布一個新的APK。如果你想要在原有的基礎上創建一個全新的可單獨發布的應用,你應該選擇product flavors,否則,你應該使用build types。

創建product flavors

創建product flavors和創建build types十分相似。你可以通過添加productFlavor塊來實現:

android {
    productFlavors {
        red {
            applicationId 'com.gradleforandroid.red'
            versionCode 3
        }
        blue {
            applicationId 'com.gradleforandroid.blue'
            minSdkVersion 14
            versionCode 4
        }
    }
}

Product flavors和build types相比有不同的屬性。這是因為product flavors是ProductFlavor類的對象,就像所有構建腳本中的defaultConfig對象一樣。這意味著defaultConfig和你所有的product flavors擁有同樣的屬性。

Source sets

和構建類型一樣,product flavors也可以有自己的源碼集目錄。你只需要創建一個和flavor同名的文件夾即可。你甚至可以更進一步,創建一個build type和flavor名稱結合起來的目錄。名稱結合時,flavor在前,build type在后。比如,你想為release版本,blueflavor的app設置一個不一樣的圖標,則結合后的源碼集目錄為blueRelease。該目錄優先級高于單獨的release目錄或者blue目錄。

復合的flavor變體

有些時候,你想要更進一步創建product flavor的組合。舉個例子,客戶A和客戶B想同時擁有免費和付費版本的應用,并且兩個客戶的商標不同。創建四個不同的flavors會導致重復的設置。這時,組合flvors是一個高效的方式,只需要為flavor設置不同的維度就可以了:

android {
    flavorDimensions "color", "price"

    productFlavors {
        red {
            flavorDimension "color"
        }
        blue {
            flavorDimension "color"
        }
        free {
            flavorDimension "price"
        }
        paid {
            flavorDimension "price"
        }
    }
}

一旦使用了flavorDimensions,你需要為每個flavor指定一個維度,否則會產生編譯錯誤。flavorDimensions定義了一組維度,它們的順序很重要。在組合兩個flavors時,它們可能定義了相同的屬性或者資源。這種情況下,維度的順序決定了優先使用哪個flavor的配置。在上面的例子中,color維度會覆蓋price維度。維度的順序也決定了構建變體的名稱。上例產生的構建變體如下:

  • blueFreeDebugblueFreeRelease
  • bluePaidDebugbluePaidRelease
  • redFreeDebugredFreeRelease
  • redPaidDebugredPaidRelease

Build variants(構建變體)

構建變體是build types和product flavors簡單組合的結果。每當你創建一個新的build type或者product flavor,一個新的變體也會被創建。比如,你有標準的debugrelease構建變量,你又創建了redblue兩個product flavors,這時將會創建下面的構建變體:

<center>
圖1 Android Studio構建變體窗口

圖1 Android Studio構建變體窗口</center>

這是Android Studio的Build Variants窗口的截圖。該窗口列出了所有的構建變體,并允許你切換它們。點擊Run按鈕將會運行你所選擇的構建變體。

如果你沒有product flavors,構建變體將只包含構建類型。你不可以沒有任何構建類型。即使你沒有定義自己的構建類型,Android插件也會為app或者library創建debug構建類型。

依賴

每個構建類型有它自己的依賴。Gradle自動為每個構建類型創建一個新的依賴配置。如果你只想為debug版本添加一個日志框架,可以這樣做:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.2.0'
    debugCompile 'de.mindpipe.android:android-logging-log4j:1.0.3'
}

你可以用這種方式將構建類型和依賴配置組合起來使用,比如stagingProvidedreleaseApk等。這使你可以得到非常具體的依賴項。

Product flavor也可以有自己的依賴配置,定義方式同構建類型,如freeCompile等。

然而如果你想為一個包含構建類型和Product flavor的構建變體添加依賴配置,你需要在configurations塊中初始化這個配置。下面為freeDebug變體添加apk依賴:

configurations {
    // Initializes a placeholder for the freeDebugApk dependency configuration.
    freeDebugApk {}
}

dependencies {
    freeDebugApk fileTree(dir: 'libs', include: ['*.jar'])
}

任務

Gradle Android插件會為每個構建變體創建任務。新建的Android應用默認有debugrelease構建類型,所以你可以使用assembleDebugassembleRelease任務來創建對應的APK,或者使用assemble任務同時創建兩個。當你添加新的構建類型時,新的任務也會被創建。一旦你添加了flavors,一系列新的任務也會被創建。因為構建類型的任務需要和product flavor的任務結合起來。這意味著對于只有一個構建類型和一個flavor的簡單配置,你已經有了三個任務來構建所有的構建變體:

  • assembleBlue使用blue flavor的配置,同時assemble BlueReleaseBlueDebug
  • assembleDebug使用debug構建類型的配置,為每個product flavor assemble一個debug版本。
  • assembleBlueDebug結合了blue flavor和debug構建類型的配置,并且flavor設置會覆蓋構建類型的設置。

Source sets

由一個構建類型和一個或多個product flavor組成的構建變體也可以有自己的源碼集目錄。比如由debug構建類型,blue flavor和free flavor組成的構建變體,源碼集目錄為src/blueFreeDebug/java/。你也可以在sourceSets塊中修改目錄位置,這在第一章出現過。

合并資源和manifest文件

源碼集的引入增加了構建過程的復雜度。Gradle Android插件在打包app前需要合并main源碼集和構建類型的源碼集。除此之外,庫工程也可能提供額外資源,它們也需要合并進來。這同樣適用于manifest文件。比如,你在debug變體的app中可能需要額外的Android權限來保存日志文件,而你并不想在main源碼集中聲明這些權限,因為這可能引起潛在用戶的抵觸。作為替代方案,你需要在debug構建類型的源碼集中添加manifest文件來聲明這些權限。

資源和manifest文件的優先級順序如下:

Build type > Flavor > Main > Dependencies

如果一個資源同時在flavor和main的源碼集中聲明,那么flavor中的資源有更高的優先級。這種情況下,flavor源碼集中的資源會被打包,main源碼集中的不會。庫工程中聲明的資源優先級最低。

合并資源和manifest文件還有許多需要學習的地方。如果你想學習更多細節,可以閱讀官方文檔:http://tools.android.com/tech-docs/new-build-system/user-guide/manifest-merger

創建構建變體

Gradle可以很容易地處理復雜的構建變體。即使是創建并配置兩個構建類型和兩個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"
        }
    }
}

本例我們創建了四個構建變體:blueDebug,blueStaging,redDebugredStaging

變體過濾器

你也可以在構建中完全忽略某個變體。這樣,你就可以加快assemble命令構建所有變體的速度,并且去掉不用執行的任務。這也可以保證在Android Studio的構建變體窗口(見圖1)中不會出現這個變體的選項。

你可以在build.gradle文件中添加如下代碼來過濾掉構建變體:

android.variantFilter { variant ->
    if(variant.buildType.name.equals('release')) {
        variant.getFlavors().each() { flavor ->
            if (flavor.name.equals('blue')) {
                variant.setIgnore(true);
            }
        }
    }
}

本例中,我們首先檢查變體的構建類型是不是release,然后我們檢查該構建類型的所有flavors。getFlavors()方法返回一個flavor數組,數組的長度等于flavor的維度數。比如,對于blueFreeDebug變體而言,flavor數組包含bluefree兩個flavor。本例過濾掉了blueFreeReleasebluePaidRelease兩個變體。運行gradlew tasks命令,將不會看到這兩個變體相關的任務。

簽名配置

在你將應用發布到Google Play或其他應用市場之前,你需要用私鑰進行簽名。如果你有多個不同的版本,你需要為每個flavor使用不同的私鑰進行簽名。這就需要使用簽名配置了。

android {
    signingConfigs {
        staging.initWith(signingConfigs.debug)

        release {
            storeFile file("release.keystore")
            storePassword"secretpassword"
            keyAlias "gradleforandroid"
            keyPassword "secretpassword"
        }
    }
}

本例我們創建了兩個不同的簽名配置。

Android插件會默認配置一個名為debug的簽名配置,使用一個通用的公開密碼的keystore文件,所以沒有必要再為debug構建類型創建一個簽名配置。

staging配置調用了initWith()函數,該函數通過傳入的配置初始化當前配置。這就表示staging使用了和debug相同的簽名配置。

release配置使用storeFile來指定keystore文件的路徑,同時定義了key別名和用到的密碼。

就像前面提到的,在構建文件中保存證書不是很好的設計。推薦保存在Gradle properties文件中。第七章會用一大塊去講解處理簽名配置密碼的一個任務。

在定義了簽名配置之后,你需要將其應用到構建類型或者flavor中。構建類型和flavor都有一個名為signingConfig的屬性:

android {
    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }
}

本例使用了構建類型。如果你想為每個flavor使用不同的證書,你需要創建不同的簽名配置。你可以用同樣的方式定義它們:

android {
    productFlavors {
        blue {
            signingConfig signingConfigs.release
        }
    }
}

通過這種方式使用簽名配置會出現問題。為flavor設置簽名配置時,會覆蓋構建類型的簽名配置。更好的方式是為每個構建類型每個flavor設置不同的配置:

android {
    buildTypes {
        release {
            productFlavors.red.signingConfig signingConfigs.red
            productFlavors.blue.signingConfig signingConfigs.blue
        }
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,497評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,305評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,962評論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,727評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,193評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,411評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,945評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,777評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,978評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,216評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,657評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,960評論 2 373

推薦閱讀更多精彩內容

  • 當你在開發一個app,通常你會有幾個版本。大多數情況是你需要一個開發版本,用來測試app和弄清它的質量,然后還需要...
    justCode_閱讀 444評論 0 2
  • 第四篇( 構建變體 ) 當你在開發一個app,通常你會有幾個版本。大多數情況是你需要一個開發版本,用來測試app和...
    一劍飛鴻閱讀 688評論 0 0
  • 轉載注明出處:http://www.lxweimin.com/p/5255b100930e 0. 前言 完全由個人翻...
    王三的貓阿德閱讀 2,537評論 0 4
  • 1.介紹 如果你正在查閱build.gradle文件的所有可選項,請點擊這里進行查閱:DSL參考 1.1新構建系統...
    Chuckiefan閱讀 12,159評論 8 72
  • 當你在開發一個app,通常你會有幾個版本。大多數情況是你需要一個開發版本,用來測試app和弄清它的質量,然后還需要...
    雪殘閱讀 410評論 0 0