最近接觸到自定義構建型別 BuildType,發現這一塊有些地方稍不注意的話會被繞進去浪費點時間,既然我這邊已經花費時間了,如果正好你也需要接觸到 BuildType,也許接下來分享的 tips 可能會幫你節省些時間。
緣起
BuildType 相信許多開發者都不陌生,很常見的一種使用場景是線上、線下的後臺介面 BaseUrl 不同,許多人會選擇在 build.gradle 檔案的 buildTypes 中定義全域性變數來實現線上線下環境的定義(Gradle 2.x 版本),例如:
buildTypes {
debug {
buildConfigField "String", "BASE_URL", ""http://debug.api/""
}
release {
buildConfigField "String", "BASE_URL", ""https://release.api/""
}
}
複製程式碼
在開發過程中,除了預設的 Debug 和 Release 版本,我們可能還需要為程式自定義一些東西。比如在上線 release 版本前,還需要一個預釋出版本,該版本除了後臺介面的 BaseUrl 與線上版本不同外,其他資源(包括資料庫環境)都與線上相同,該版本用來做釋出前的最後測試,最大程度避免線上環境出問題。如果每次打預發版本都去直接修改程式碼中的 BaseUrl 很明顯不是最優解。有一種解決方案是自定義 BuildType,在 app 模組下的 build.gradle 的 buildTypes 中自定義新的構建版本:
buildTypes {
debug {
buildConfigField "String", "BASE_URL", ""http://debug.api/""
}
release {
buildConfigField "String", "BASE_URL", ""https://release.api/""
}
pre.initWith(release)
pre {
buildConfigField "String", "BASE_URL", ""https://pre.api/""
}
}
//java 類中呼叫 BuildConfig.BASE_URL 獲取定義的變數
複製程式碼
initWith()
是 BuildType 的一項配置項,我們還可以看到上文中提到的buildConfigField
其實也是一項配置項。該配置可以理解成initWith(release)
可以理解成拷貝了 release 這一構建型別的所有變數,因為我們知道,每一個構建型別都有一些預設的變數,例如debuggable
、zipAlignEnabled
等,使用該配置就免去為新增的構建型別定義所有的變數。
定義了新的構建型別後,gradle會自動生成新的task,使用gradle assemblePre
即可打包新定義的預發包。這裡需要稍微注意的地方就是,必須在 app 模組下的 build.gradle 中定義新的構建型別,gradle 才會生成新的task。
Moduel 中自定義 BuildType 的問題
隨著現在模組化開發越來越流行,許多專案都會將一些業務無關的模組獨立出去,作為 Moduel 在專案中依賴使用,以此達到複用的效果。很常見的例如網路庫可能就會被獨立出來,那麼上文中的關於就會被定義在子 Module 中。這裡不注意的話就會浪費一些時間。假設你在 app 模組與子模組的 build.gradle 的 buildTypes 中都如上文定義了三種型別 debug
、release、pre 版本的BASE_URL
,請注意:
如果你執行了gradle assemblePre
,沒錯是構建了 pre 版本,但是列印出日誌你會發現:
app.BuildConfig.BASE_URL = "https://pre.api/"
module.BuildConfig.BASE_URL = "https://release.api/"
複製程式碼
子模組中如果沒有特別指定構建版本,無論你執行的是gradle assemblePre
還是gradle assembleDebug
,構建的都是 release 版本。可以使用defaultPublishConfig
配置指定需要構建的版本,例如在子模組的 build.gradle 中指定:
android {
...
//指定構建版本
defaultPublishConfig "pre"
...
}
複製程式碼
當然最好設定個變數,否則如果有許多子模組,不可能修改構建版本時一個一個改過去,常用的是在專案最外層的 build.gradle 中設定變數供子模組呼叫:
buildscript {
...
dependencies {
classpath `com.android.tools.build:gradle:2.3.3`
}
...
}
ext {
projectBuildType = "debug"
}
//子模組中引用變數
defaultPublishConfig rootProject.ext.projectBuildType
複製程式碼
這裡需要注意,一旦子模組中指定了構建型別,例如 pre 版本,則該模組的 buildTypes 中必須也要有對應的構建型別 pre,否則編譯不通過。並且,一旦指定了構建型別,則該模組的構建型別就只會是指定的型別。舉個例子:子模組中指定了defaultPublishConfig "pre"
,執行gradle assembleRelease
,列印日誌:
app.BuildConfig.BASE_URL = "https://release.api/"
module.BuildConfig.BASE_URL = "https://pre.api/"
複製程式碼
所以這裡就會比較煩人,每次打不同的包都需要去修改projectBuildType
的值,還是需要手動修改。
分支名決定構建的版本型別
想要隔離手動修改,網上看到過一種解決方案:
子模組的 build.gradle 中設定:
android {
publishNonDefault true
}
複製程式碼
然後在模組的依賴處,例如 app 模組的 build.gradle 中改進依賴的寫法:
releaseCompile project(path: `:module`, configuration: `release`)
debugCompile project(path: `:module`, configuration: `debug`)
複製程式碼
這樣就可以實現構建時子模組與 app 模組的型別一致。但這種寫法太煩了,如果你有十來個依賴,還得一個一個寫過去,又如果你還自定義了不止一種構建型別,且沒新增一個新的 buildType 都得修改所有的依賴,我認為也不是最優解。
這裡提供一個方案僅供參考。先介紹一下我們的開發流程,例如新開發1.0版本。首先從 master 切出一條 devel1.0 分支用於前期開發階段,開發完畢達到上線標準後,切出一條 pre1.0 預發分支,打預發包做最後測試並做最後的 bug 修復,最後測試通過,合併程式碼到 master 分支,打線上包釋出至應用商店。不同階段對應不同的分支,所以不同的構建版本可以通過分支名來決定,git 肯定可以獲取當前分支名,而 grale 中則可以執行 cmd 命令,二者結合即可達到想要的效果。有這個思路後實現起來就很簡單:
ext {
projectBuildType = "debug"
def gitBranchName = "git rev-parse --abbrev-ref HEAD".execute().text.trim()
if(gitBranchName.contains("master")) {
projectBuildType = "release"
} else if(gitBranchName.contains("pre")) {
projectBuildType = "pre"
}
}
複製程式碼
Gradle 3.0.0 帶來的問題
Android Studio 3.0 + Gradle 3.0 相信許多人都躍躍欲試。升級到 Gradle 3.0 可能需要做一些改動,詳情可見Migrate to Android Plugin for Gradle 3.0.0。
Gradle3.0 中自定義 BuildType 有需要注意的地方
Cause of build error
Your app includes a build type that a library dependency does not.
在 Gradle 2.x 時代,如果 app 中定義了 pre 型別,而子模組中沒有定義,是不會報錯的。但在 Gradle 3.0 下,如果你的 app 包含了新的自定義的 buildType,而依賴庫中卻沒有相應的自定義 buildType,則編譯階段就會報錯。
一種解決方案是在子模組裡也定義 app 中的所有 buildType,當然,專案裡依賴多的同學肯定要吐槽了:我懶!不想修改辣麼多東西!
Gradle 提供了比 2.x 時代更智慧的相容方案:matchingFallbacks
。
在 buildTypes 中定義 matchingFallbacks
,可以在子模組沒找到當前構建型別時指定要載入哪個型別
//app 模組下的 build.gradle 中
buildTypes {
debug {
buildConfigField "String", "BASE_URL", ""http://debug.api/""
}
release {
buildConfigField "String", "BASE_URL", ""https://release.api/""
}
pre.initWith(release)
pre {
buildConfigField "String", "BASE_URL", ""https://pre.api/""
matchingFallbacks = [`pre`, `debug`, `release`]
}
}
複製程式碼
matchingFallbacks 可以定義多個構建型別,當執行gradle assemblePre
構建 Pre 版本時,而恰巧某個子模組又沒有定義 pre 版本,則會按照你指定的 matchingFallbacks 從前往後依次尋找,直到型別匹配。這種匹配方案比 Gradle 2.x 時代預設為你構建 release 版本要智慧的多,至少我們還可以根據變數來在構建不同型別時定義不同的匹配順序。並且 Gradle 3.x 中前文提到的defaultPublishConfig
配置已經不再生效了,構建時子模組的構建型別與 app 構建型別保持一致(前提是子模組中也定義了該型別),變得更加靈活。
技術終歸是在向前發展的。關於自定義 BuildType 的一些使用小貼士就是這些了,至於更多的關於構建型別相關的知識,例如 buildTypes 結合 productFlavors,或是定義 sourceSets 屬性指定不同的程式碼目錄、資原始檔目錄等知識以後有機會再開一篇聊吧(又挖坑2333)。
囉嗦了一堆,權且算是拋磚引玉。吼啦,下篇部落格見~