以前看到一些自動化版本號打包的文章。如果您的專案是用 Git 管理的,並且恰巧又是使用 Gradle 編譯(應該絕大部分都是這樣的了吧?),本文試圖找到一種更加優雅的自動版本管理方法。
1 背景
我們都知道,Android 應用的版本管理是依賴 AndroidManifest.xml 中的兩個屬性:
android:versionCode
:版本號,是一個大於 0 的整數,相當於 Build Number,隨著版本的更新,這個必須是遞增的。大的版本號,覆蓋更新小的版本號;android:versionName
:版本名,是一個字串,例如"1.2.0"
,這個是給人看的版本名,系統並不關心這個值,但是合理的版本名,對後期的維護和 bug 修復也非常重要。
在使用了 Android Studio 或者 Gradle 編譯以後,我們通常是在 build.gradle 裡面定義這兩個值,如下:
1 2 3 4 5 6 7 8 |
android { ... defaultConfig { ... versionCode 1 versionName "1.0" } } |
2 自動版本號
在這篇文章中 6 tips to speed up your Gradle build 發現了,可以使用 Git 中 commit 的數量來作為版本號(versionCode)。方案如下:
1 2 3 4 5 6 7 8 |
def cmd = 'git rev-list HEAD --first-parent --count' def gitVersion = cmd.execute().text.trim().toInteger() android { defaultConfig { versionCode gitVersion } } |
這裡關鍵是這一行 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。舉例一個簡單的例子,假如你的當前程式碼狀態如下:
1 2 3 |
--A--B-...-C--> | | v1.0 v1.1 |
執行 git describe
的結果是:v1.1
,如果是如下的情況:
1 2 3 |
--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 中的使用如下:
1 2 3 4 5 6 7 8 |
def cmd = 'git describe --tags' def version = cmd.execute().text.trim() android { defaultConfig { versionName version } } |
這樣就可以自動抽取 git 中的 tag 為版本名了。有些同學可能接受不了這樣版本名字 v1.1-1-gXXXXXX
,這裡也可以稍微做一些修改,使版本號更好看,如下:
1 2 3 4 5 6 7 8 |
def pattern = "-(\d+)-g" def matcher = version =~ pattern if (matcher) { version = version.substring(0, matcher.start()) + "." + matcher[0][1] } else { version = version + ".0" } |
這樣的話,上面的版本名就變為了 v1.0.0
和 v1.1.1
了。
4 優化
前面的那篇文章中說了,為了儘可能減少 gradle 指令碼的運算,提高開發速度,我們可以把這樣的自動版本的計算放到 release 編譯中去。最後的寫法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
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() } } } |
至此,結合 git 和 gradle 我們就實現了自動版本號。