1. 程式人生 > >Android 自定義構建型別 BuildType

Android 自定義構建型別 BuildType

最近接觸到自定義構建型別 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/””
}
}
1
2
3
4
5
6
7
8
在開發過程中,除了預設的 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 獲取定義的變數
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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/
1
2
子模組中如果沒有特別指定構建版本,無論你執行的是gradle assemblePre還是gradle assembleDebug,構建的都是 release 版本。可以使用defaultPublishConfig配置指定需要構建的版本,例如在子模組的 build.gradle 中指定:

android {

//指定構建版本
defaultPublishConfig “pre”

}
1
2
3
4
5
6
當然最好設定個變數,否則如果有許多子模組,不可能修改構建版本時一個一個改過去,常用的是在專案最外層的 build.gradle 中設定變數供子模組呼叫:

buildscript {

dependencies {
classpath ‘com.android.tools.build:gradle:2.3.3’
}

}
ext {
projectBuildType = “debug”
}

//子模組中引用變數
defaultPublishConfig rootProject.ext.projectBuildType
1
2
3
4
5
6
7
8
9
10
11
12
13
這裡需要注意,一旦子模組中指定了構建型別,例如 pre 版本,則該模組的 buildTypes 中必須也要有對應的構建型別 pre,否則編譯不通過。並且,一旦指定了構建型別,則該模組的構建型別就只會是指定的型別。舉個栗子:子模組中指定了defaultPublishConfig “pre”,執行gradle assembleRelease,列印日誌:

app.BuildConfig.BASE_URL = “https://release.api/
module.BuildConfig.BASE_URL = “https://pre.api/
1
2
所以這裡就會比較煩人,每次打不同的包都需要去修改projectBuildType的值,還是需要手動修改。

分支名決定構建的版本型別
想要隔離手動修改,網上看到過一種解決方案:
子模組的 build.gradle 中設定:

android {
publishNonDefault true
}
1
2
3
然後在模組的依賴處,例如 app 模組的 build.gradle 中改進依賴的寫法:

releaseCompile project(path: ‘:module’, configuration: ‘release’)
debugCompile project(path: ‘:module’, configuration: ‘debug’)
1
2
這樣就可以實現構建時子模組與 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"
}

}
1
2
3
4
5
6
7
8
9
10
11
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']
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
matchingFallbacks 可以定義多個構建型別,當執行gradle assemblePre 構建 Pre 版本時,而恰巧某個子模組又沒有定義 pre 版本,則會一一按照你指定的 matchingFallbacks 從前往後依次尋找,直到型別匹配。這種匹配方案比 Gradle 2.x 時代預設為你構建 release 版本要智慧的多,至少我們還可以根據變數來在構建不同型別時定義不同的匹配順序。並且我們也可以拋棄掉前文提到的defaultPublishConfig配置了,因為 Gradle 3.x 中,只要 gradle 構建時,子模組的構建型別變成了與 app 構建型別一致了(前提是子模組中也定義了該型別),變得更加靈活。

技術終歸是在向前發展的。關於自定義 BuildType 的一些使用小貼士就是這些了,至於更多的關於構建型別相關的知識,例如 buildTypes 結合 productFlavors,或是定義 sourceSets 屬性指定不同的程式碼目錄、資原始檔目錄等知識以後有機會再開一篇聊吧(又挖坑2333)。

囉嗦了一堆,權且算是拋磚引玉。吼啦,下篇部落格見~