Android应用编译时自动生成版本号
2017-05-02 13:20
459 查看
近期有一个工作任务:按照某个规则,给Android应用设置一个在编译时自动生成的versionCode与versionName。
这一点倒是不奇怪,很多正式的应用都有自己的一套版本号管理。市面上什么某某应用几点零,就是这样的一个产物。
我这个任务的难度除了自动生成,还有一个附加条件:在Android项目中编译(通过Android.mk)和在Android Studio中编译(通过build.gradle)时,规则要保持一致。
这样就有意思多了。
versionName在开发过程中可以起到辨识作用,方便开发工程师与其他团队的人交流,也是用户直接可见的。比如某个问题是某某版本上出现的,某个功能是某某版本添加的。
versionCode在人的圈子里其实没太大作用,但是它是Android系统真正使用的值。在更新时,新的版本versionCode不能比原来的小,否则更新会失败。另外要注意,int是有上限的,不能太乱来。
versionCode和versionName,直接体现在APK中的AndroidManifest.xml文件中。可以通过以下命令查看:
修改versionCode和versionName,最简单、直观的方法,就是改源代码中的AndroidManifest.xml。但是,这样就需要频繁修改AndroidManifest.xml,每次版本更新时改一次。如果你希望每次提交的版本号都不一样,那么每次提交都需要密集地修改那两行,这显然是不可取的。
此外,实际上无论是在Android项目内编译,还是在IDE编译,最终都是用
在Android源码中编译时,如果一个模块没有指定versionCode与versionName,那么将与平台相同。比如在Android Marshmallow (API level 23)中编译,那么versionCode就是23,versionName就是PLATFORM_VERSION,比如6.0.1。
很多不在意这些的小项目,直接默认versionName为1.0,至死不变——这其实也算是一个规则。
比较常见的还有先标为0.1,开发到一段时间再标为1.0并发布,如果有bug修正后叫1.1,增加一堆新功能后叫2.0。
这些版本号规则,简单实用,通过改文件的方式就可以实现,不需要自动化。
以下以一个比较复杂的来举例。
其中,aa是大版本号,bb是小版本号,最多2位,不足不补0。它们由人来自定义,必要时手动修改。比如说,某某应用v2.0,那么aa为2,bb为0。版本号设计里,有一部分是需要保留手工控制的,不然产品经理会因控制欲无法满足而抓狂。
cccc是Git库的提交次数,xxxxx是Git库HEAD的SHA1码前5位。这些都是随着Git提交而确定的,在编译过程中可以、也应该自动生成。在shell中执行以下命令可以获取:
yymmdd是日期,yy是年的后两位,mm是月,dd是日,不足补0。
如果是用命令行手工打包,写个脚本就行了。而如果是在IDE或项目里编译,则需要修改编译文件。
Android.mk的语法,其实就是最原始的makefile,添加了一些自定义的东西。目前对普通开发者来说,也就NDK开发时有可能会接触。但是在Android源码编译时,每一个小模块都有至少一个这东西。
由于Android.mk的内置函数和变量里,并没有versionCode和versionName这两个值的对应,所以需要通过
除此之外,就没有Android.mk特有的东西了,全是makefile自有的语法。
Gradle中使用的是Groovy语言,这是一种基于JVM的敏捷开发语言,还算是易学易用的。
在项目的build.gradle中,
在build.gradle文件底部,统一实现版本号自动生成管理:
这样虽然比较简陋,但功能是有了。
可以编译后用
这个文件是Android Studio自动生成的,会打包进APK中,可以在Java源码中直接引用。它受到编译开关控制,不同编译条件下会有不同的
./app/build/generated/source/buildConfig/androidTest/debug/com/example/xxx/test/BuildConfig.java
./app/build/generated/source/buildConfig/debug/com/example/xxx/BuildConfig.java
./app/build/generated/source/buildConfig/release/com/example/xxx/BuildConfig.java
不过,它对我来说是无用的,因为
当cccc大于10000时,在versionCode会发生进位,bb会变相增加;而在versionName,则不会发生进位,bb仍然是预设值。
假如对cccc进行除余操作(
这个问题暂时不用担心,可以等真的发生这个情况时再考虑解决。
目前,世界上第一个大型Git库——git/git它自己,目前(2016年7月14日)的提交总次数为43560。所以,假如cccc换成5位、甚至6位,这个问题可以拖延到海枯石烂。
当然,如果是我来设计,那么versionCode等于提交次数就好了,不用搞什么前缀。
在做这个任务前(甚至之后),我对Makefile与Groovy的语法细节都没有太多的了解。不过我知道两点就够了:
Android源码是靠Android.mk把各个小模块组织起来的,而它本质上是Makefile
Android Studio用的构建工具是Gradle,而Gradle实际上用的是Groovy来实现的
此后就是顺藤摸瓜。
这说明,在这些冷门的技术领域,如果只是偶尔才使用到,那么广度远比深度重要。没有必要事事精通,因为大多用不上;但至少要广泛地建立索引,有切入点才便于按需学习。
而在Makefile与Groovy里,对versionCode与versionName做那些不复杂却很麻烦的拼接时,我在shell与Java里的深入了解,帮了很大的忙。
Makefile本身设计简单、功能有限,连加减乘除都要靠shell帮忙。如果懂shell,则可快速上手;如果不知shell为何物(似乎一不小心又鄙视到了纯Windows程序员),那就麻烦多了。正如张无忌学乾坤大挪移,没有多年的九阳神功基础,哪有几个时辰后的神功大成、光明顶上耀武扬威。
Groovy与Java有很多共通之处,比如获取Git提交次数的手段:
就与Java是同样的思路:
当然,Java显然要麻烦得多。只要精通Java,Groovy自然也手到擒来。
也许有人会怀疑:即使之前对Android.mk与Gradle没有一点了解,也一样可以通过搜索引擎解决这个问题。这似乎没错,只是慢了点而已。
不过,在评估这个任务的可行性、而不是去做时,这却不成立。如果没有预先的了解,根本就不会往这个方面去想。实际上,坚持用Android Studio开发系统应用,让一个项目在Gradle下开发、在Android.mk下编译使用,在我的工作环境与所知的网络世界中,至今也只见我一人。
Android.mk:http://android.mk/
Makefile语法:http://www.gnu.org/software/make/manual/
Groovy官方文档:http://groovy-lang.org/documentation.html
Groovy执行shell命令:http://www.joergm.com/2010/09/executing-shell-commands-in-groovy/
原文链接:Android应用编译时自动生成版本号
这一点倒是不奇怪,很多正式的应用都有自己的一套版本号管理。市面上什么某某应用几点零,就是这样的一个产物。
我这个任务的难度除了自动生成,还有一个附加条件:在Android项目中编译(通过Android.mk)和在Android Studio中编译(通过build.gradle)时,规则要保持一致。
这样就有意思多了。
versionCode与versionName
Android的APK会自带一个版本描述,这就是versionCode和versionName。其中,versionCode是一个int,应该是一个随着开发而持续增长的值,而versionName是一个
String,这是给人看的版本号。
versionName在开发过程中可以起到辨识作用,方便开发工程师与其他团队的人交流,也是用户直接可见的。比如某个问题是某某版本上出现的,某个功能是某某版本添加的。
versionCode在人的圈子里其实没太大作用,但是它是Android系统真正使用的值。在更新时,新的版本versionCode不能比原来的小,否则更新会失败。另外要注意,int是有上限的,不能太乱来。
versionCode和versionName,直接体现在APK中的AndroidManifest.xml文件中。可以通过以下命令查看:
aapt dump badging PATH/TO/YOUR.apk | grep version
aapt是Android Asset Packaging Tool,就是Android应用在命令行的打包工具,SDK里就有。主要作用是把二进制文件打包为APK,也附带了一些相关的查询功能。
修改versionCode和versionName,最简单、直观的方法,就是改源代码中的AndroidManifest.xml。但是,这样就需要频繁修改AndroidManifest.xml,每次版本更新时改一次。如果你希望每次提交的版本号都不一样,那么每次提交都需要密集地修改那两行,这显然是不可取的。
此外,实际上无论是在Android项目内编译,还是在IDE编译,最终都是用
aapt来打包。打包时,可以通过设置
--version-code和
--version-name参数,来对这两个字段设值,并且在最终打包时添加到APK的AndroidManifest.xml中。
在Android源码中编译时,如果一个模块没有指定versionCode与versionName,那么将与平台相同。比如在Android Marshmallow (API level 23)中编译,那么versionCode就是23,versionName就是PLATFORM_VERSION,比如6.0.1。
版本号规则
版本号规则可以自定义,每家都不太一样。很多不在意这些的小项目,直接默认versionName为1.0,至死不变——这其实也算是一个规则。
比较常见的还有先标为0.1,开发到一段时间再标为1.0并发布,如果有bug修正后叫1.1,增加一堆新功能后叫2.0。
这些版本号规则,简单实用,通过改文件的方式就可以实现,不需要自动化。
以下以一个比较复杂的来举例。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="aa.bb.cccc.yymmdd.xxxxx" android:versionCode="aabbcccc" package="com.example.xxx">
其中,aa是大版本号,bb是小版本号,最多2位,不足不补0。它们由人来自定义,必要时手动修改。比如说,某某应用v2.0,那么aa为2,bb为0。版本号设计里,有一部分是需要保留手工控制的,不然产品经理会因控制欲无法满足而抓狂。
cccc是Git库的提交次数,xxxxx是Git库HEAD的SHA1码前5位。这些都是随着Git提交而确定的,在编译过程中可以、也应该自动生成。在shell中执行以下命令可以获取:
git rev-list --count HEAD # This is cccc git describe --always # This is short SHA1
yymmdd是日期,yy是年的后两位,mm是月,dd是日,不足补0。
如果是用命令行手工打包,写个脚本就行了。而如果是在IDE或项目里编译,则需要修改编译文件。
Android.mk中自动生成版本号
由于有Git命令牵涉其中,相对来说,Android.mk里是更容易实现的。Android.mk的语法,其实就是最原始的makefile,添加了一些自定义的东西。目前对普通开发者来说,也就NDK开发时有可能会接触。但是在Android源码编译时,每一个小模块都有至少一个这东西。
aa = 1 bb = 0 cccc = $(shell cd $(LOCAL_PATH) && git rev-list --count HEAD) version_code = $(shell expr $(aa) \* 1000000 + $(bb) \* 10000 + $(cccc)) version_name := $(aa).$(bb).$(cccc).$(shell date +%y%m%d).$(shell cd $(LOCAL_PATH) && git describe --always) LOCAL_AAPT_FLAGS += --version-code $(version_code) LOCAL_AAPT_FLAGS += --version-name $(version_name)
由于Android.mk的内置函数和变量里,并没有versionCode和versionName这两个值的对应,所以需要通过
LOCAL_AAPT_FLAGS来设置
aapt的参数。
除此之外,就没有Android.mk特有的东西了,全是makefile自有的语法。
build.gradle中自动生成版本号
Android Studio中,gradle.build里的设定会覆盖AndroidManifest.xml中的设置。Gradle中使用的是Groovy语言,这是一种基于JVM的敏捷开发语言,还算是易学易用的。
在项目的build.gradle中,
android.defaultConfig里,把versionCode和versionName改成自定义函数
getSelfDefinedVersion:
android { compileSdkVersion 23 buildToolsVersion "23.0.3" defaultConfig { applicationId "com.example.xxx" minSdkVersion 22 targetSdkVersion 23 versionCode getSelfDefinedVersion("code") versionName getSelfDefinedVersion("name") } buildTypes { release { minifyEnabled false } } }
在build.gradle文件底部,统一实现版本号自动生成管理:
def getSelfDefinedVersion(type) { int aa = 1 int bb = 0 Process process = "git rev-list --count HEAD".execute() process.waitFor() int cccc = process.getText().toInteger() if ("code".equals(type)) { aa * 1000000 + bb * 10000 + cccc } else if ("name".equals(type)) { String today = new Date().format("yyMMdd") process = "git describe --always".execute() process.waitFor() String sha1 = process.getText().trim() "$aa.$bb.$cccc.$today.$sha1" } }
这样虽然比较简陋,但功能是有了。
可以编译后用
aapt查看结果,也可以在gradle sync后,查看
app/build/目录下自动生成的
BuildConfig.java文件。
package com.example.xxx; public final class BuildConfig { public static final boolean DEBUG = Boolean.parseBoolean("true"); public static final String APPLICATION_ID = "com.example.xxx"; public static final String BUILD_TYPE = "debug"; public static final String FLAVOR = ""; public static final int VERSION_CODE = 1000172; public static final String VERSION_NAME = "1.0.172.160713.08d62e0"; }
这个文件是Android Studio自动生成的,会打包进APK中,可以在Java源码中直接引用。它受到编译开关控制,不同编译条件下会有不同的
BuildConfig.java文件产生。
./app/build/generated/source/buildConfig/androidTest/debug/com/example/xxx/test/BuildConfig.java
./app/build/generated/source/buildConfig/debug/com/example/xxx/BuildConfig.java
./app/build/generated/source/buildConfig/release/com/example/xxx/BuildConfig.java
不过,它对我来说是无用的,因为
BuildConfig.java在项目里用Android.mk编译时不会产生。如果像我一样有兼容两边的需求,调用它就是死路一条。要兼容两套编译方法还有其它的坑,这里就不详述了。
隐患
目前的实现方案,其实有一个已知的隐患。当cccc大于10000时,在versionCode会发生进位,bb会变相增加;而在versionName,则不会发生进位,bb仍然是预设值。
假如对cccc进行除余操作(
cccc %= 10000),那么在每次跨越10000时,versionCode的自增性会被破坏。所以每次发生这种情况,都要手动增加一下bb。所以,目前暂时采用前一种方式,至少保证无人工干预时,versionCode的自增性不变。
这个问题暂时不用担心,可以等真的发生这个情况时再考虑解决。
目前,世界上第一个大型Git库——git/git它自己,目前(2016年7月14日)的提交总次数为43560。所以,假如cccc换成5位、甚至6位,这个问题可以拖延到海枯石烂。
当然,如果是我来设计,那么versionCode等于提交次数就好了,不用搞什么前缀。
总结
最后谈一谈技术之外的事:如何迅速在完全不懂的领域学以致用。在做这个任务前(甚至之后),我对Makefile与Groovy的语法细节都没有太多的了解。不过我知道两点就够了:
Android源码是靠Android.mk把各个小模块组织起来的,而它本质上是Makefile
Android Studio用的构建工具是Gradle,而Gradle实际上用的是Groovy来实现的
此后就是顺藤摸瓜。
这说明,在这些冷门的技术领域,如果只是偶尔才使用到,那么广度远比深度重要。没有必要事事精通,因为大多用不上;但至少要广泛地建立索引,有切入点才便于按需学习。
而在Makefile与Groovy里,对versionCode与versionName做那些不复杂却很麻烦的拼接时,我在shell与Java里的深入了解,帮了很大的忙。
Makefile本身设计简单、功能有限,连加减乘除都要靠shell帮忙。如果懂shell,则可快速上手;如果不知shell为何物(似乎一不小心又鄙视到了纯Windows程序员),那就麻烦多了。正如张无忌学乾坤大挪移,没有多年的九阳神功基础,哪有几个时辰后的神功大成、光明顶上耀武扬威。
Groovy与Java有很多共通之处,比如获取Git提交次数的手段:
Process process = "git rev-list --count HEAD".execute() process.waitFor() int cccc = process.getText().toInteger()
就与Java是同样的思路:
Runtime runtime = Runtime.getRuntime(); try { Process process = runtime.exec("git rev-list --count HEAD"); process.waitFor(); InputStream inputStream = process.getInputStream(); byte[] bytes = new byte[128]; inputStream.read(bytes, 0, 128); String countStr = new String(bytes).trim(); int cccc = Integer.parseInt(countStr); } catch (IOException | InterruptedException e) { e.printStackTrace(); }
当然,Java显然要麻烦得多。只要精通Java,Groovy自然也手到擒来。
也许有人会怀疑:即使之前对Android.mk与Gradle没有一点了解,也一样可以通过搜索引擎解决这个问题。这似乎没错,只是慢了点而已。
不过,在评估这个任务的可行性、而不是去做时,这却不成立。如果没有预先的了解,根本就不会往这个方面去想。实际上,坚持用Android Studio开发系统应用,让一个项目在Gradle下开发、在Android.mk下编译使用,在我的工作环境与所知的网络世界中,至今也只见我一人。
参考链接
英文还是要啃的,官网还是要上的。Android.mk:http://android.mk/
Makefile语法:http://www.gnu.org/software/make/manual/
Groovy官方文档:http://groovy-lang.org/documentation.html
Groovy执行shell命令:http://www.joergm.com/2010/09/executing-shell-commands-in-groovy/
原文链接:Android应用编译时自动生成版本号
相关文章推荐
- php脚本生成google play url的下载链接,下载apk并自动反编译后获取android版本号
- Android之版本-APP编译时自动生成版本号
- 《Android 自动编译、打包生成apk文件 2 - 使用原生Ant方式》
- Android 自动编译、打包生成apk文件 4 - 多渠道批量打包
- Android 应用获取Jenkins编译的版本号
- android项目中配置NDK自动编译生成so文件
- (!标)用Ant自动生成多个Android应用包.
- Android 自动编译、打包生成apk文件 1 - 命令行方式
- 在Windows下用ant编译Android应用生成apk安装包
- Android 自动编译、打包生成apk文件 1 - 命令行方式
- Android 自动编译、打包生成apk文件 3 - 使用SDK Ant方式
- jean同学的android编译自动修改版本号
- VC编译时版本号自动生成
- Android 自动编译、打包生成apk文件 2 - 使用原生Ant方式
- android 应用开发不能自动生成R.java的原因
- Android 自动编译、打包生成apk文件 3 - 使用SDK Ant方式
- Android工程的自动创建,编译并生成apk
- Android 自动编译、打包生成apk文件 4 - 多渠道批量打包
- Android 自动编译、打包生成apk文件 1 - 命令行方式
- 【Android】打包过程:生成自动代码->编译->(混淆)->dex文件->生成资源文件->打apk包->(签名)->对齐