Android App打包自動生成版本號Versin Name與Version Code方法之完美方案 - 孟帥

jopen 8年前發布 | 74K 次閱讀 Android開發 移動開發

本文主要介紹了三個方案用于使用使用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的命令行

  1. 倉庫提交總數

    git rev-list --all --count

    git rev-list --all是列出當前版本庫的所有commit,也就是.git目錄里面的refs/下的所有提交,與當前的分支無關。--count就是wc -l 統計總共個數。

  2. 當前分支提交總數

    git rev-list  --count HEAD

    上面的會返回當前所在分支的提交總數。

  3. 生成版本號
    這個是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.9

    git 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周前我的女兒降生了,然后因為突然當爹一直感慨人生、感慨生命之神奇。。。沒有進入工作狀態。后面我覺得要自己鼓勵自己把寫博客的習慣堅持下去。

來自: http://my.oschina.net/mengshuai/blog/551356

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!