自动版本号发布

自动版本号发布

[TOC]

背景

由于手动升级版本号等操作容易被遗忘和操作错误,,所以要找到一个可以自动管理版本号的方式.

内容

版本控制主要有这么两个属性:

  • android:versionCode : 整数,是内部版本号,用于判断一个版本是否比另一个版本新,用户并不需要看到。通常是跟随着发布版本增加该code。如果安装的app 的 versionCode 比当前该 app 的 versionCode 小的话会导致安装失败,提示无法降级安装.

  • android:versionName : 字符串,用户能看到的版本号。如 1.0.0 .系统不关心这个值.

方案

方案1 使用 Git 提交记录来生成

自动化版本号代码

用 Git 的话,我们可以使用 Git 中的 commit 数量作为 versionCode . 如下 :

1
2
3
4
5
6
7
8
9
// 获取当前分支的 commit 数量.
def cmdVersionCode = 'git rev-list HEAD --count'
def versionCodeValue = cmdVersionCode.execute().text.trim().toInteger()
android {
defaultConfig {
versionCode versionCodeValue
}
}

这个方案要求开发者要及时提交代码,否则会出现版本号不增加的情况,不利于维护.

有些开发者会担心一次版本发布中会有很多次 commit 的情况.首先这个 versionCode 的类型是 int 类型的,最大值是 2^31-1 , 大约可以达到 21亿.

自动化版本名

使用该方法的话需要在 Git 中标记 tag , tag 就是版本名.这样也有利于后期追溯版本.

1
2
3
4
5
6
7
8
9
// 获取当前 commit 到距离他最近的 tag 描述.
def cmdVersionName = 'git describe --tags'
def versionNameValue = cmdVersionName.execute().text.trim()
android {
defaultConfig {
versionName versionNameValue
}
}

这个时候版本名的后面可能会带上 g..... 的一串字符串,我们可以针对这个情况做一个修改,让版本名更加通俗易懂.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 获取tag 后面跟上的 距离上个 tag 提交次数,以及提交码.
def pattern = "-(\\d+)-g"
def matcher = versionNameValue =~ pattern
if (matcher) {
// 将距离上个 tag 的提交次数变为版本名.
// 以多少为基数.
def baseNum = 20
def thirdNum = matcher[0][1].toInteger().intdiv(baseNum)
def fourthNum = matcher[0][1].toInteger() % baseNum
versionNameValue = versionNameValue.substring(0, matcher.start()) + "." + thirdNum + "."+fourthNum
// 直接以距离上个 tag 的提交次数为第三个版本号.
// versionNameValue = versionNameValue.substring(0, matcher.start()) + "." + matcher[0][1]
} else {
versionNameValue = versionNameValue + ".0"
}

由于直接把距离上个tag 的提交次数作为版本号会导致版本号增长过快,所以将他拆分为两部分.上面的例子以20为基数,比如此时距离上个提交次数为 32 , 上一个版本为 v1.0 , 那么生成的版本名就是 v1.0.1.12 ,而不是 1.0.32

还要注意一点,这样写的画每次调试的debug版本也会生成不同的版本名,可能会导致有接入线上统计的版本出现繁杂冗余的情况.所以可以加上一个判断版本的方式,如下:

现在项目的总 build.gradle 文件中添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 通过taskName判断当前操作是否是在打release包
def checkRelease() {
def runTasks = gradle.startParameter.taskNames
for (String task : runTasks) {
// 执行“assemble”并且非“debug”的任务就是执行“release”的任务
if (task.contains("assemble") && !task.contains("Debug")) {
// targetTask = task
return true
}
}
return false
}
ext {
// 定义当前gradle文件的全局变量, 是否是release版本
isRelease = checkRelease()
}

然后在 module 的 build.gradle 中修改之前的方法为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if (matcher) {
if (isRelease) {
// 将距离上个 tag 的提交次数变为版本名.
// 以多少为基数.
def baseNum = 20
def thirdNum = matcher[0][1].toInteger().intdiv(baseNum)
def fourthNum = matcher[0][1].toInteger().mod(baseNum)
versionNameValue = versionNameValue.substring(0, matcher.start()) + "." + thirdNum + "." + fourthNum
}else {
versionNameValue = "0.0.0"
}
} else {
versionNameValue = versionNameValue + ".0"
}

这样debug 的时候版本名称就都是0.0.0了.

方案2 使用配置文件叠加

该方法的思路是利用 property 文件进行保存版本号等信息,然后在每次生成 release 的时候进行叠加.

以下是 autoversion.gradle 文件. 在你的 module 的 gradle 文件中引入该文件就可以了.

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
51
52
53
54
55
56
57
58
59
60
61
62
63
apply from: "util.gradle"
ext {
versionFile = new File(project.rootDir, 'version.properties')
calculateVersionName = {
if (checkRelease()) {
incrementVersionNumber()
}
def version = readVersion()
return "${version['major']}.${version['minor']}.${version['build']}"
}
calculateVersionCode = {
def version = readVersion()
def major = version['major'] as int // 1..∞
def minor = version['minor'] as int // 0..99
def build = version['build'] as int // 0..999
return (major * 100 + minor) * 1000 + build
}
}
Properties readVersion() {
def version = new Properties()
def stream
try {
stream = new FileInputStream(versionFile)
version.load(stream)
} catch (FileNotFoundException ignore) {
} finally {
if (stream != null) stream.close()
}
// safety defaults in case file is missing
if (!version['major']) version['major'] = "1"
if (!version['minor']) version['minor'] = "0"
if (!version['build']) version['build'] = "0"
return version
}
void incrementVersionNumber() {
def version = readVersion()
// careful with the types, culprits: "9"++ = ":", "9" + 1 = "91"
def build = version['build'] as int
build++
version['build'] = build.toString()
def stream = new FileOutputStream(versionFile)
try {
version.store(stream, null)
} finally {
stream.close()
}
}
if (plugins.hasPlugin('android') || plugins.hasPlugin('android-library')) {
android {
defaultConfig {
versionName = calculateVersionName()
versionCode = calculateVersionCode()
}
}
}

以下是 autoversion.gradle 引用的 工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ext {
checkRelease = this.&checkRelease
}
// 通过taskName判断当前操作是否是在打release包
def checkRelease() {
def runTasks = gradle.startParameter.taskNames
for (String task : runTasks) {
// 执行“assemble”并且非“debug”的任务就是执行“release”的任务
if (task.contains("assemble") && !task.contains("Debug")) {
// targetTask = task
return true
}
}
return false
}

这样子有一些不太理想的是,它是使用 版本名来生成版本号的,有可能出现同一份代码多次打包后出现多个不同的版本.所以可以考虑和第一种Git 版本的方式结合.

修改后的版本

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
apply from: "../../AndroidShareLib/gradle/util.gradle"
ext {
versionFile = new File(project.rootDir, 'version.properties')
calculateVersionName = {
def version = readVersion()
def code = version['versioncode'] as int
if ((code < versionCode)) {
incrementVersionNumber()
}
return "${version['major']}.${version['minor']}.${version['build']}"
}
calculateVersionCode = {
// 获取当前分支的 commit 数量.
def cmdVersionCode = 'git rev-list HEAD --count'
return cmdVersionCode.execute().text.trim().toInteger()
}
}
Properties readVersion() {
def version = new Properties()
def stream
try {
stream = new FileInputStream(versionFile)
version.load(stream)
} catch (FileNotFoundException ignore) {
} finally {
if (stream != null) stream.close()
}
// safety defaults in case file is missing
if (!version['major']) version['major'] = "1"
if (!version['minor']) version['minor'] = "0"
if (!version['build']) version['build'] = "0"
if (!version['versioncode']) version['versioncode'] = "0"
return version
}
void incrementVersionNumber() {
def version = readVersion()
// careful with the types, culprits: "9"++ = ":", "9" + 1 = "91"
def build = version['build'] as int
build++
version['build'] = build.toString()
version['versioncode'] = versionCode as String
def stream = new FileOutputStream(versionFile)
try {
version.store(stream, null)
} finally {
stream.close()
}
}
if (plugins.hasPlugin('android') || plugins.hasPlugin('android-library')) {
android {
defaultConfig {
versionName = calculateVersionName()
versionCode = calculateVersionCode()
}
}
}

Reference

APP 版本控制

About Me

我的博客 leonchen1024.com

我的 GitHub https://github.com/LeonChen1024

微信公众号

wechat

You forgot to set the business and currency_code for Paypal. Please set it in _config.yml.
You forgot to set the url Patreon. Please set it in _config.yml.
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×