更优雅的 Android 发布自动版本号方案

更新时间:2016-07-13 17:38:25 点击次数:1882次

以前看到一些自动化版本号打包的文章。如果您的项目是用 Git 管理的,并且恰巧又是使用 Gradle 编译(应该绝大部分都是这样的了吧?),本文试图找到一种更加优雅的自动版本管理方法。

1 背景

我们都知道,Android 应用的版本管理是依赖 AndroidManifest.xml 中的两个属性:

在使用了 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 --count'  
def gitVersion = cmd.execute().text.trim().toInteger()

android {  
  defaultConfig {
    versionCode gitVersion
  }
} 

这里关键是这一行 git 命令 git rev-list HEAD --count,表示获取当前分支的 commit 数量。

注:谢谢 @zhangls 提醒。原文直接使用了 6 tips to speed up your Gradle build 中的命令 git rev-list HEAD --first-parent --count,也就是带了 --first-parent 选项。发现使用此选项并不合理,比如 这篇文章 中提到的,first-parent 可能会出现不稳定的情况,这就可能导致获得的 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
  }
} 

这样就可以自动抽取 git 中的 tag 为版本名了。有些同学可能接受不了这样版本名字 v1.1-1-gXXXXXX,这里也可以稍微做一些修改,使版本号更好看,如下:

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 编译中去。后的写法如下:

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 我们就实现了自动版本号。

本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责,本站只提供参考并不构成任何投资及应用建议。本站是一个个人学习交流的平台,网站上部分文章为转载,并不用于任何商业目的,我们已经尽可能的对作者和来源进行了通告,但是能力有限或疏忽,造成漏登,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

回到顶部
嘿,我来帮您!