Android App打包自動生成版本號Versin Name與Version Code方法之完美方案 - 孟帥
本文主要介紹了三個方案用于使用使用Gradle自動化生成VersionCode與VersionName,最終使用VersionName與git版本庫關聯,并且區分開發版本與發布版本的解決方案。
需求背景
- 我們公司Android研發團隊Coding共8人,負責多個項目的開發,其中有一個App需要至少6人都參與其中Coding
- 手機客戶端升級部署在umeng上,每次通過測試后要上傳新版本,umeng sdk根據versionCode對比是否讓手機彈出框提示升級.
- 手機客戶端集成了fabric sdk用于錯誤分析。
- 測試人員或者根據市場反饋情況要去umeng或者fabric查看最近的崩潰日志,并轉交給研發人員。
面臨問題
- 研發人員簽名App之前,對發布的App中的VersionCode、VersionName定義沒有規范,經常出現重復情況。
- 測試人員看到Crash日志之后,無法直接確定是歷史上線版本、當前測試版本、研發內部版本,經常被忽悠
- 研發人員對于正式上線版本的版本號例如V1.3.21,在git repo里面無法找到對應的代碼。
- 在git提交之前實際上并無新的git commit id出來,在這期間(開發調測期間)打的包要與之前的包區分開。
解決方案
這本身是個細心能解決的問題,但是例如強制打Tag,強制寫發布日志,但是是人都會犯錯,而技術是工具,技術就是應該去降低別人犯錯誤的機會
方案1
技術原理
在打包操作時(不是run),解析AndroidManifest.xml
,將versionCode在每次gradle打包時自加1
代碼實現
只是修改VersionCode
task('increaseVersionCode') << {
def manifestFile = file("AndroidManifest.xml")
def pattern = Pattern.compile("versionCode=\"(\\d+)\"")
def manifestText = manifestFile.getText()
def matcher = pattern.matcher(manifestText)
matcher.find()
def versionCode = Integer.parseInt(matcher.group(1))
def manifestContent = matcher.replaceAll("versionCode=\"" + ++versionCode + "\"")
manifestFile.write(manifestContent)
}
tasks.whenTaskAdded { task ->
if (task.name == 'generateReleaseBuildConfig') {
task.dependsOn 'increaseVersionCode'
}
}
當然如果需要同時修改VersionName那么無非在此優化下
import java.util.regex.Pattern
task('increaseVersionCode') << {
def manifestFile = file("src/main/AndroidManifest.xml")
def pattern = Pattern.compile("versionCode=\"(\\d+)\"")
def manifestText = manifestFile.getText()
def matcher = pattern.matcher(manifestText)
matcher.find()
def versionCode = Integer.parseInt(matcher.group(1))
def manifestContent = matcher.replaceAll("versionCode=\"" + ++versionCode + "\"")
manifestFile.write(manifestContent)
}
task('incrementVersionName') << {
def manifestFile = file("src/main/AndroidManifest.xml")
def patternVersionNumber = Pattern.compile("versionName=\"(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)\"")
def manifestText = manifestFile.getText()
def matcherVersionNumber = patternVersionNumber.matcher(manifestText)
matcherVersionNumber.find()
def majorVersion = Integer.parseInt(matcherVersionNumber.group(1))
def minorVersion = Integer.parseInt(matcherVersionNumber.group(2))
def pointVersion = Integer.parseInt(matcherVersionNumber.group(3))
def buildVersion = Integer.parseInt(matcherVersionNumber.group(4))
def mNextVersionName = majorVersion + "." + minorVersion + "." + pointVersion + "." + (buildVersion + 1)
def manifestContent = matcherVersionNumber.replaceAll("versionName=\"" + mNextVersionName + "\"")
manifestFile.write(manifestContent)
}
tasks.whenTaskAdded { task ->
if (task.name == 'generateReleaseBuildConfig' || task.name == 'generateDebugBuildConfig') {
task.dependsOn 'increaseVersionCode'
task.dependsOn 'incrementVersionName'
}
}
缺陷
尼瑪兩個人一起用的時候,就沒卵用了。該方案只適合個人開發者
方案二
技術原理
引入外部文件存儲versionCode,然后該文件一起上傳到git repo。
代碼實現
創建文件version.properties,放置在build.gradle同級目錄,并在文件中寫入VERSION_CODE=8
該方法DEMO項目地址
android {
compileSdkVersion 18
buildToolsVersion "18.1.0"
def versionPropsFile = file('version.properties')
if (versionPropsFile.canRead()) {
def Properties versionProps = new Properties()
versionProps.load(new FileInputStream(versionPropsFile))
def code = versionProps['VERSION_CODE'].toInteger() + 1
versionProps['VERSION_CODE']=code.toString()
versionProps.store(versionPropsFile.newWriter(), null)
defaultConfig {
versionCode code
versionName "1.1"
minSdkVersion 14
targetSdkVersion 18
}
}
else {
throw new GradleException("Could not read version.properties!")
}
// rest of android block goes here
}
缺陷
操作復雜,無法關聯git日志,verionCode只能用于升級操作。
終極解決方案三
技術原理
首先是介紹兩個git的命令行
-
倉庫提交總數
git rev-list --all --count
git rev-list --all是列出當前版本庫的所有commit,也就是.git目錄里面的refs/下的所有提交,與當前的分支無關。--count就是wc -l 統計總共個數。
-
當前分支提交總數
git rev-list --count HEAD
上面的會返回當前所在分支的提交總數。
-
生成版本號
這個是git里面吊炸天的顯示距離最近提交的數量以及自動命名的命令行git describe --tags --dirty
當然使用這個命令的前提是你有至少1個tag在你的repo里面。
例如:git tag
輸出的所有版本號是
V1.4.6_279
V1.4.7
V1.4.7.1
V1.4.8
V1.4.9git describe --tags --dirty
輸出:
V1.4.9-50-gad266df-dirty
分段解釋下:
V1.4.9
代表我最近打的一個tag的名稱
-50
是距離這個tag有50次提交
-gad266df
是我最后一次提交的commit hash是ad266dfd22846d627f6cac7543ada4ed3de45759,取前幾位,前面的g不算。
-dirty
是說我修改了代碼沒有執行git commit。。也就是尼瑪程序員開發過程中的調試版本。
我第一次執行這個命令后就驚呆了,git居然有這么人性化又智能的東西。
有了這幾個git命令行就可以寫gradle腳本了。
代碼實現
定義變量獲取versionCode與versionName
def getVersionCode = { ->
try {
def stdout = new ByteArrayOutputStream()
exec {
//此處可以根據實際情況使用git rev-list --all --count
commandLine 'git', 'rev-list', '--first-parent', '--count', 'HEAD'
standardOutput = stdout
}
//+300是因為與之前的版本命名區分開,不會與之前的重復
return Integer.parseInt(stdout.toString().trim())+300
}
catch (ignored) {
println "===================error code!"
return -1;
}
}
def getVersionName = { ->
try {
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'describe', '--tags', '--dirty'
standardOutput = stdout
}
return stdout.toString().trim()
}
catch (ignored) {
println "===================error name!"
return null;
}
}
配置打包的versionCode與versionName
android {
....
defaultConfig {
...
versionName getVersionName()
versionCode getVersionCode()
}
....
}
方案三完整demo點擊這里
首先為當前版本打個tag,然后run運行一下,然后查看下生成的版本號與版本名稱。大功告成!
注意,在git提交時要提交下本地的tag
以后在umeng或者fabric中看到一個V1.4.9-50-gad266df的崩潰日志,馬上就可以定位到是哪個哥們寫的代碼,并且是在哪個文件哪一行造成的,后期是否修復也都可以在git的log里面看到。如果是dirty結尾的,就一定是內部開發的,基本都可以忽略掉。所有研發人員都可以不用管到底如何命名版本的問題,專心干該干的事。
并且如果大版本更新,重新本地打個新tag提交即可。
結束語:這篇文章寫了將近2周時間,因為2周前我的女兒降生了,然后因為突然當爹一直感慨人生、感慨生命之神奇。。。沒有進入工作狀態。后面我覺得要自己鼓勵自己把寫博客的習慣堅持下去。