自動產生 VersionCode 與 VersionName
透過 gradle,改良一個更好的版本訊息更新流程
編譯 Android 程式時會在 gradle build script 裡頭決定該打包好的 apk 版本相關訊息,其實就是 versionCode
和 versionName
。
versionName
是顯示給使用者看的版本號,像是 1.0、1.1 之類的經典組合;而 versionCode
是一組數字,Android 系統會透過 versionCode
的大小來判斷哪一個版本是比較新的,並且會阻止使用者降級安裝比較舊的版本。另外,Google Play 商店也是透過 versionCode
來判斷新上傳的 apk 是不是新版。
上次分享的文章:在 build APK 省去繁文縟節,透過 signingConfig 的設定可以省去一些手動設定 keyStore 位置、Alias Name 和輸入密碼等繁雜的設置時間,直接透過 build APK 指令無痛編譯。雖然看起來似乎不錯,但是應該還有很多可以調整個空間。例如每次編譯時,都需要重新更新過 versionName
與 versionCode
。而且,如果有測試的階段還要為測試版加入特殊的字串,例如 beta release 會習慣在 versionName
後頭加上特殊的字串用來辨識。還需要在每次發布新版時,更新 versionCode
裡頭的數字。
本篇來分享幾個我曾經使用過的做法,讓編譯的過程之中自動產生 versionCode
。總過嘗試過兩種方式,一種是直接產生 timestamp,另一個則稍微把玩了 gradle —— 由 gradle 的變數編輯 versionName
,解決 timeStamp 法遇到的問題。
先從產生 timeStamp 的方式來介紹:
這個方法為:每次 Build 時產生一組新的 versionCode
,由編譯的時間點生成 TimeStamp。這種做法的優點是快速、方便,設定一次後就不用再理 versionCode
調整,可以說是設定好後就可以不理它。
不過缺點也是滿明顯的,不能透過 versionCode
反推出是哪一個版本。 versionName
還是需要手動輸入,但真正的問題是 Google Play 商店對 versionCode
的限制。Google Play 商店對 versionCode
有最大值 2100000000 ,超過這個數字便沒有辦法繼續更新架上的 App。
這表示 TimeStamp 的 Pattern 不能設定為 yyMMddHHmm
,也就是年月日時分。因為只要這個 app 到 2021 年就沒辦法繼續更新下去。(還是這已經是太奢侈的煩惱? 😫)
後來才注意到這點的我,只能在 timeStamp 前加上 19 這個 placeholder,防止 2021 年 versionCode
進展到無法更新的地步。
如此一來就犧牲了 timeStamp 後面的 mm
(分鐘),即同一個小時內編譯的 apk 都會是同一組 versionCode
,需要特別注意。
本範例將 groovy 程式碼寫在其他的 gradle 檔案,可以在 build.gradle 引入外部的 gradle 檔案來使用裡頭的方法。
我將這個 script 取名為 build-script.gradle
,放在根目錄的 gradle 資料夾裡頭。在 build.gradle 加入引入外部 gradle 來使用外部 gradle 寫好的方法。
apply from: "${rootDir}/gradle/build-script.gradle"
這樣只要在 defaultConfig 的 versionCode
設定使用 getVersionCodeTimeStamps()
這個方法即可。
先撇除一個小時內 versionCode
都會一樣的問題, versionName
還是需要自己輸入。不同的測試階段還需要不忘加上 beta 等測試版前綴詞,我的工作夥伴 TIng-Han Su (ender503)想到另一個直覺的作法,解決 timeStamp 法遇到的狀況:
- 自動產生
versionName
- 反推程式版本 (這個算解決一半)
- 避免 2021 年更新末日
不過這個作法會需要一些手動,沒有 timeStamp 產生大法這麼的快狠爽。
新的作法如返璞歸真,簡單好理解。
僅藉由將 versionName
之中的 <major>.<minor>.<patch> 數值分別獨立出來變成變數,編譯 apk 時透過 gradle 程式碼去代組 versionCode
和 versionName
。
首先,在專案根目錄的 build.gradle 之中增加幾個變數:
同樣,新增兩個方法分別處理 versionCode
和 versionName
的自動生成。這裡用的方法借鑒至 Jake Wharton 的 Decomposed Version Name and Code 作法,依據我們的情境做了一些調整。
放入 defaultConfig 的 versionCode
和 versionName
的設定之中:
對比 Jake Wharton 的版本
def versionMajor = 2
def versionMinor = 8
def versionPatch = 0
// bump for dogfood builds, public betas, etc.
def versionBuild = 0
android {
defaultConfig {
versionCode versionMajor * 10000 + versionMinor * 1000 + versionPatch * 100 + versionBuild versionName "${versionMajor}.${versionMinor}.${versionPatch}"
}
}
Jake Wharton 的版本在編譯 Pre-production 版的 app 時,會將 versionBuild 加到 versionCode
裡頭。
假設程式版本 2.8.0 進入到測試階段,編譯 2.8.0 beta 1 , versionCode
將會是 28001。經過了顛簸與坡折,最後的 beta 版本是 beta 7,也就是 versionCode
為 28007。最後,釋出 2.8.0 正式版, versionCode
會是 28000 顯然會造成 Android 安裝與 Google Play 檢查都沒辦法通過的窘境。(因為 versionCode
較小不被視為一個新版)
透過 TIng-Han Su (ender503)的方法就不會有這個問題。
同樣以 2.8.0 版本的開發為例,2.8.0 beta 1 versionCode
將會是 2079901,最終的 2.8.0 beta 7 versionCode
會是 2079907。測試階段完成,發布 2.8.0 正式版, versionCode
為 2080000,可以正常安裝升級!
但是我們的用法在 minor、patch 和 beta 數字超過 100 時會溢位,所以 minor、patch 和 beta 最大只能設定為 99。(Jake Wharton 的則是 10,他的理由如下所示。)
If you have more than 10 patch or minor versions you have other problems.
— Jake Wharton
你可以在這個 Kotlin Playground 自由操作驗算。 😆
因為這幾個版本號碼都化為變數,在產生 versionName
時就可以直接透過 beta_number
這個變數大於 0 來判斷是否進入 beta pre-production 階段。在編譯 beta 版時,就能直接將我們預先設定好的 postfix_name
加到 versionName
之中。
但缺點也很明顯:無法從 versionCode
來反推 pre-production 的版本號。只能反推 production 版本的 versionCode
。例如上述的 2.8.0 versionCode
為 2080000。推測接下來的幾個正式版 release, versionCode
可能為:
2.8.0, versionCode is 2080000
2.8.1, versionCode is 2080100
2.8.2, versionCode is 2080200
2.9.0, versionCode is 2090000
2.9.10, versionCode is 2091000
如上所示,可以反推回去 versionName
。
所以如果你的情境與我們類似,只關注 production app 的 versionCode
,也許這個作法才會比較適合。
而且,因為這個作法只有 7 碼的數字,不會有 TimeStamp 直出會遇到的2100000000 極限。
這樣設定後,推薦於每次 bump version commit 時調整這幾個變數,就完成 versionCode
和 versionName
更新。