更優雅的 Android 發布自動版本號方案
來自: http://www.race604.com/android-auto-version/
以前看到一些自動化版本號打包的文章。如果您的項目是用 Git 管理的,并且恰巧又是使用 Gradle 編譯(應該絕大部分都是這樣的了吧?),本文試圖找到一種更加優雅的自動版本管理方法。
1 背景
我們都知道,Android 應用的版本管理是依賴 AndroidManifest.xml 中的兩個屬性:
- android:versionCode :版本號,是一個大于 0 的整數,相當于 Build Number,隨著版本的更新,這個必須是遞增的。大的版本號,覆蓋更新小的版本號;
- android:versionName :版本名,是一個字符串,例如 "1.2.0" ,這個是給人看的版本名,系統并不關心這個值,但是合理的版本名,對后期的維護和 bug 修復也非常重要。
在使用了 Android Studio 或者 Gradle 編譯以后,我們通常是在 build.gradle 里面定義這兩個值,如下:
android { ... defaultConfig { ... versionCode 1 versionName "1.0" } }
2 自動版本號
在這篇文章中 6 tips to speed up your Gradle build 發現了,可以使用 Git 中 commit 的數量來作為版本號(versionCode)。方案如下:
def cmd = 'git rev-list HEAD --first-parent --count'
def gitVersion = cmd.execute().text.trim().toInteger()android {
defaultConfig { versionCode gitVersion } }</pre>這里關鍵是這一行 git 命令 git rev-list HEAD --first-parent --count ,表示獲取當前分支的 commit 數量。
這是一個絕妙的方案。因為在項目開發中,我們的往 Git 庫中提交的 Commit 的數量應該是只增不減的(當然,在極少的情況下有例外),而且對應 Commit 的數量直接對應代碼當前的版本狀態,只要你做了代碼修改,版本號就應該增加。有些解決方案中,每次 Build 就會增加一次版本號,個人感覺并不合適,如果是相同的代碼,發布出去版本號應該保持一致,而不在于你編譯多少次。
另外,有些人可能會擔心,每次版本發布,可能會包含幾百個新的 commit,這樣的話 versionCode 會不會增長太快了,最后導致不夠用了。其實,完全沒有必要擔心,versionCode 是 int 類型,最大值是 2^31-1 ,也就是 21 億多,Android 源碼中,改動最活躍的 framework/base 所有分支到目前為止也就 20 萬多個 commit,所以完全夠用了。
3 自動版本名
前面通過一條簡單的命令實現了自動化的 versionCode ,現在我們看怎么自動化 versionName 。
在正常的發布流程中,在發布新版本的時候,都會在版本庫中打 tag。一般情況下,tag 名就是版本名,而且也建議這么做,因為如果某個版本出現 bug,也可以正好 checkout 這個 tag 來查看代碼。所以,現在的問題就是怎么自動獲得 git 庫中最新的最新 tag?原來,git 早就提供了命令 git describe ,它的功能就是獲取從當期 commit 到距離它最近的 tag 的描述。默認都是 annoted tag,如果要指所有的類型的 tag 的話,就加 --tags 參數。
此命令的詳細介紹在這里: git-describe 。舉例一個簡單的例子,假如你的當前代碼狀態如下:
--A--B-...-C--> | | v1.0 v1.1執行 git describe 的結果是: v1.1 ,如果是如下的情況:
--A--B-...-C--D--> | | v1.0 v1.1執行 git describe 的結果是: v1.1-1-gXXXXXX ,其中 1 表示當前代碼距離最近的 tag v1.1 一個 commit,最新的 commit 的 id 是 XXXXXX 。
可見, describe 命令很好的描述了當前的分支的版本狀態,我們可以直接使用這個它的輸出作為版本號。在 build.gradle 中的使用如下:
def cmd = 'git describe --tags'
def version = cmd.execute().text.trim()android {
defaultConfig { versionName version } }</pre>這樣就可以自動抽取 git 中的 tag 為版本名了。有些同學可能接受不了這樣版本名字 v1.1-1-gXXXXXX ,這里也可以稍微做一些修改,使版本號更好看,如下:
def pattern = "-(\d+)-g"
def matcher = version =~ patternif (matcher) {
version = version.substring(0, matcher.start()) + "." + matcher[0][1] } else { version = version + ".0" }</pre>這樣的話,上面的版本名就變為了 v1.0.0 和 v1.1.1 了。
4 優化
前面的那篇文章中說了,為了盡可能減少 gradle 腳本的運算,提高開發速度,我們可以把這樣的自動版本的計算放到 release 編譯中去。最后的寫法如下:
def gitVersionCode() {
def cmd = 'git rev-list HEAD --first-parent --count' cmd.execute().text.trim().toInteger() }def gitVersionTag() {
def cmd = 'git describe --tags' def version = cmd.execute().text.trim()def pattern = "-(\\d+)-g" def matcher = version =~ pattern if (matcher) { version = version.substring(0, matcher.start()) + "." + matcher[0][1] } else { version = version + ".0" } return version
}
android {
compileSdkVersion 23 buildToolsVersion "23.0.2"defaultConfig { applicationId "com.race604.example" minSdkVersion 15 targetSdkVersion 23 versionCode 1 versionName '1.0' } buildTypes { debug { // 為了不和 release 版本沖突 applicationIdSuffix ".debug" } release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } applicationVariants.all { variant -> if (variant.buildType.name.equals('release')) { variant.mergedFlavor.versionCode = gitVersionCode() variant.mergedFlavor.versionName = gitVersionTag() } }
}</pre>
至此,結合 git 和 gradle 我們就實現了自動版本號。
</div>