App版本号自动生成

Android中的App版号

在Android中,一般应用都是在build时通过写入versionCodeversionName来显示标注。
在设备端,我们也可以通过adb shell dumpsys package 包名的方式来查看对应的信息。

1
2
3
4
5
6
7
8
adb shell dumpsys package XXX.AAA.BBBBBBB.CCCC
===> Package [XXX.AAA.BBBBBBB.CCCC] (4a1bd18):
userId=10090
pkg=Package{90b0371 XXX.AAA.BBBBBBB.CCCC}
......
versionCode=1102120023 minSdk=27 targetSdk=28
versionName=v2.12.23.191220.AABBCC.DDDDDD
......

其中,我们可以看到versionCode = 1102120023,而versionName=v2.12.23.191220.AABBCC.DDDDDD
很明显,一般来说versionCode是给机器看的,是一个递增的数字,而versionName是给人看的,是一串有实际意义的字符串。

Gradle下的Android Compile

目前大部分的Android都是通过Gradle进行编译打包的,因此针对这一块内容的修改,就相应的转变为Gradle的操作了。一般来说,versionCodeversionName的部分会集成在defaultConfig块中,我们可以在对应modulebuild.gradle文件中看到如下的代码块:

1
2
3
4
5
6
7
8
9
10
11
android {
......
defaultConfig {
......
versionCode 112233445555
versionName "helloworld_version"
......
}
......
}

通过这种方式定义的versionCodeversionName最终反映到module中就是BuildConfig.java

1
2
3
4
5
6
public final class BuildConfig {
....
public static final int VERSION_CODE = 112233445555;
public static final String VERSION_NAME = "helloworld_version";
....
}

客制化VersionName & VersionCode

有了以上的认知,针对versionCodeversionName我们可以做一些适当的调整,以便输出结合具体项目和编译的版本等关键信息,方便研发确认版本,追溯问题。
针对git工具,我们提到一个版本时,我们谈论的到底是什么?

  • 编译时的最新提交
  • 编译的日期
  • 编译的分支

除此之外,针对Android应用,可能还会有打包渠道,分发渠道等等信息。
仅仅针对上述的三点,编译时环境,举例说明如何通过gradle获取到对应的信息。

获取简短commit hash值

1
2
3
def static getShortCommitId() {
return 'git rev-parse --short HEAD'.execute().text.trim()
}

git rev-parse –short HEAD
=> c819b1f

diff with

git log -1
=> commit c819b1fe08e3ed261e834cdab8cf86505aec32d7

获取本地的分支branch名

1
2
3
4
5
def static getLocalBranchName() {
def result = "git symbolic-ref --short -q HEAD".execute().text.trim()
result += ""
return result
}

获取当下的提交数

可以根据传入的日期获取从那天开始到现在的提交数

1
2
3
4
5
6
7
8
9
def static getCommitCount(String date,String path="") {
def mainCountCmd = ["git", "log", "--pretty=format:%h", "--after='$date'",path]
if (org.gradle.internal.os.OperatingSystem.current().isWindows()) {
return mainCountCmd.execute().pipeTo("find /v \"\" /c".execute()).text.toInteger()
} else {
return mainCountCmd.execute().pipeTo("wc -l".execute()).text.toInteger() + 1
}
}

这边会根据windows和linux平台做一下区分,因为会用到find以及wc命令做统计

获取当前时间

1
2
3
4
5
6
def static buildTime() {
def year = new Date().format("yyyy", TimeZone.getTimeZone("GMT+08:00")).toString().toInteger()
def month = new Date().format("MM", TimeZone.getTimeZone("GMT+08:00")).toString().toInteger()
def day = new Date().format("dd", TimeZone.getTimeZone("GMT+08:00")).toString().toInteger()
return [year,month,day]
}

客制化

通过以上的4个函数,我们基本拿到了当前环境下所有的编译相关信息了。

  • 编译时的当天日期
  • 编译时最新的commit hash
  • 编译时所在的branch
  • 编译启动时与相对日期间的提交计数

我们可以通过简单的组合,来构成我们的versionCodeversionName了。
举一个简单的例子,比如说某款App自2017年8月21日启动研发。

  • 每年发布一款大版本,N
  • 每个月发布一个小版本,M
  • 每天可能会发布若干个内测版本,P

对于研发来说,结合app日志需要能还原出当时的编译环境

  • 编译时的分支,B
  • 编译时的日期,D
  • 编译时的最新commit,C

这样,我们可以确定出versionCodeversionName的大概样子了

versionCode = NMP,其中如果每个数字都占三位,那么算法上就是
versionCode = N*1000*1000+M*1000+P

相应的,versionName可能是长这样

versionName = v.NMP.B.D.C

sample code:

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
{
timeList = buildTime()
big = timeList[0] - 2017;// start from 2018
sub = timeList[1]
birthday = timeList[0]+"/"+timeList[1]+"/1"
verCode = genVerCode(big, sub, birthday)
verName = genVerName(big, sub, birthday)
## 我们把最后得到的verCode和verName赋给defaultConfig中的变量即可
##
## versionCode=2012025 minSdk=27 targetSdk=28
## versionName=v2012025.191220.master.d9552391
##
}
def static genVerCode(int big, int sub, String birthday) {
return big * 1*1000*1000 + sub *1000 + getCommitCount(birthday)
}
def static genVerName(int big, int sub, String birthday) {
def today = new Date().format("yyMMdd")
def commitCount = getCommitCount(birthday)
def branchName = getLocalBranchName()
def shortId = getShortCommitId()
return "v$piggy.$labs.$commitCount.$today.$branchName.$shortId"
}
def static getCommitCount(String date,String path="") {
def mainCountCmd = ["git", "log", "--pretty=format:%h", "--after='$date'",path]
if (org.gradle.internal.os.OperatingSystem.current().isWindows()) {
return mainCountCmd.execute().pipeTo("find /v \"\" /c".execute()).text.toInteger()
} else {
return mainCountCmd.execute().pipeTo("wc -l".execute()).text.toInteger() + 1
}
}
def static getShortCommitId() {
return 'git rev-parse --short HEAD'.execute().text.trim()
}
def static getLocalBranchName() {
def result = "git symbolic-ref --short -q HEAD".execute().text.trim()
result += ""
return result
}
def static buildTime() {
def year = new Date().format("yyyy", TimeZone.getTimeZone("GMT+08:00")).toString().toInteger()
def month = new Date().format("MM", TimeZone.getTimeZone("GMT+08:00")).toString().toInteger()
def day = new Date().format("dd", TimeZone.getTimeZone("GMT+08:00")).toString().toInteger()
return [year,month,day]
}