工作日志(2018-03)

     年后的每一天都有周一综合征.

Tuesday 20th

     这次总结关于 gradle, 第一次研究, 踩了好多坑.

     这两天一直在研究 Grade 的相关内容, 源于一个优化需求: 我们公司的产品的版本号分为五段(别问我为什么, 就是这么任性..) 例如, “1.1.1.1.002”. 可以理解为 product.major.minor.patch.custom. 其中 product 表示的是产品线, custom 表示的是内部号, 仅在团队内部, 开发和测试童鞋间使用.
     产品的童鞋是这么要求的, 每次给我们 .apk 包(也就是 release 版的包)的时候, custom 字段自动增加(姑且先自动加一吧). 这个样子就不需要手动去设置了.
     跟大神讨论了一下, Gradle 无所不能, 肯定是可以实现的, 于是乎屁颠屁颠的查官方文档.

     这里附上链接. Gradle 官方文档. 里面有一个 Android 的专栏. 这里没有直接给出 Android 专栏的地址, 因为文档会维护更新, url 也会变化.
     官方文档仅仅是对 Android 项目中使用的 build.gradle 文件做了一个简单的介绍, 并没有深入. 但是给出了参考资料. Gradle Recipes for Android, by Ken Kousen and published by O’Reilly Media, Inc 相当于 Grade 文档在 Android 项目中的是用详解. 这个也是我的主要参考资料, 写的很不错, 毕竟是官方人员提供.
     还有一本参考书, 人气博主飞雪无情的 <<Android Gradle权威指南>>, 还有他的博客 Android Gradle 实用技巧(二) 自动生成版本信息. 但是个人感觉写的一般, 对新人不友好, 我觉得这本书称之为 <<Android Gradle 权威总结>> 比较合适, 并不是指南.

     回到刚才的需求问题. 飞雪无情的博客中已经给出了具体的思路:
     大致如下:

  1. 在项目目录下新建一个 version.properties 的属性文件。
  2. 把版本名称分成三部分 major.minor.patch,版本号分成一部分number,然后在 version.properties 中新增四个KV键值对,其key就是我们上面分好的 majorminorpatch 以及 numbervalue 是对应的值。
  3. 然后在 build.gradle 里新建两个方法,用于读取该属性文件,获取对应Key的值,然后把major.minor.patch这三个key拼接成版本名称,number 用于版本号。
  4. 以上就达到了获取版本信息的目的,获取使用之后,我们还要更新我们存放在 version.properties 文件中的信息,这样就可以达到版本自增的目的,以供下次使用。
  5. 在更新版本名称三部分的时候,你可以自定义自己的逻辑,是逢10高位+1呢,还是其他算法,都可以自己灵活定义。
  6. 使用版本信息,更新 version.properties 文件的时机,记得 doLast 这个方法。
  7. 记得不会在自己运行调试的时候让你的版本信息自增哦,如何控制呢?就是要区分是真正的打包发版,还是平时的调试、测试,有很多办法来区分的。

     (观其大略, 不要在乎那几个字段, 我直接 copy 过来了)但是, 比较恶心的是, 他并没有给出实现的代码, 需要自己来搞.

     首先建立文件, 这个没什么问题.



     接着是相关读取和修改文件的方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
task autoChangeVersionCode {
// Properties 是操作 .properties 文件的工具类.
Properties versionProperties = new Properties()
// 加载 version.properties 文件, project 对应的是当前项目.
versionProperties.load(project.file('version.properties').newInputStream())
// 读取 custom 对应的字段
def custom = versionProperties.getProperty('custom')
// 然后更新字段
versionProperties.setProperty('custom', ++custom)
// 将 properties 中的内容写回文件中去. 这时踩到的第一个坑.
versionProperties.store(project.file('version.properties').newOutputStream())
}

     终端执行一下 gradlew autoChangeVersionCode (可以不区分大小写),结果没什么问题.

NOTE: 这么写并不准确, 仅仅是为了测试. 该 autoChangeVersionCode 任务是一个 配置任务, 也就是官方文档中说的 Configuration 类型的任务. 如果不加 doLast, 则任何 task 执行, 都会执行该任务. 因为执行 task 之前, 需要走一遍完整的配置. 参考的具体的官方文档, 也就是官方提供的那本书:

If you don’t want to simply configure an existing Gradle task, you need to understand the distinction between the con guration and execution phases of Gradle. During the configuration phase, Gradle builds a DAG based on their dependencies. It then exe‐ cutes the desired task, along with its dependencies. All tasks are configured before any are executed.
     — from Gradle Receipt for Android. 4.1 Writing your own custom tasks

     最后一个亟待解决的问题, 如何去调用这个方法, 应该是在执行 assembleRelease 方法之后, 自动执行 autoChangeVersionCode 任务, 参考项目中原有的修改 .apk 文件名的相关代码.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 修改打包的文件
android.applicationVariants.all { variant ->
variant.outputs.all {
if (variant.buildType.name == 'release') {
// 重新定义 .apk 文件名.
def newName = variant.name + "_" + getCustomVersionName() + "_" + releaseTime + ".apk"
outputFileName = new File(newName)
}
}
}
// 觉得这是一个可以使用的方式.
android.applicationVariants.with{
// 更新 version.properties 中的相关逻辑.
}

NOTE: 通常上述代码写在, buildType{ }release { } 中, 提示该任务是跟集成 release 版本的安装包有关. 作为一个根 task 也没有问题.

     但是, 将更新 version.properties 文件的逻辑放在 android.applicationVariants.with 中, 不管是执行 assembleDebug 或者是 assembleRelease 操作, 都会触发修改版本号的逻辑.
     小小的心塞了一下, 然后写了两个测试任务, 执行的时候发现 android.appliationVariants.with 中的逻辑仍然会执行, 也就说, android.appliationVariants.with 中的任务是一个配置任务, 不管执行什么任务都会触发执行.

     好吧, android.applicationVariants.with 是搞不定这个问题了…..
     Gradle Recipes for Android 第四章关于 gradle 任务有一个关于 copy task 的描述. (直接截图了)







     其中说明, 如果想让一个 build 任务(其他的系统任务也是可以的)执行时, copy 任务也执行, 那么可以使用这种方式定义:

1
build.dependsOn copyApks

     那么举一反三一下, 换成 assembleRelease.dependsOn autoChangeVersionCode 是不是就可以了? 尝试一下, 结果, 任性的 gradle 秒秒钟给你报错.



     无奈, 只好去问 google, Stack Overflow 中给出了一种添加系统任务依赖的方式:

1
2
3
4
5
6
tasks.whenTaskAdded { theTask ->
// 根据任务名定位具体任务.
if (theTask.name.equals('assembleRelease')) {
theTask.dependsOn autoChangeVersionCode
}
}

     然后尝试了一下, 完美!!!
     下面贴出具体的代码.

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
// 获取当前打包时间. 格式 "3-10-10h", 三月10日 10点.
static String getReleaseTime() {
def cal = Calendar.getInstance()
def strDate = (cal.get(Calendar.MONTH) + 1) + "-" +
cal.get(Calendar.DAY_OF_MONTH) + "-" +
cal.get(Calendar.HOUR_OF_DAY) + "h"
}
// 获取版本号信息
def String getCustomVersionName() {
Properties versionProperties = new Properties()
versionProperties.load(project.file('version.properties').newInputStream())
def versionName = "${versionProperties.getProperty('major')}." +
"${versionProperties.getProperty('minor')}." +
"${versionProperties.getProperty('patch')}." +
"${versionProperties.getProperty('number')}." +
"${versionProperties.getProperty('custom')}"
}
// 修改打包文件的文件名
android.applicationVariants.all { variant ->
variant.outputs.all {
// 只有在发布 release 版本的时候, 才会修改文件名
if (variant.buildType.name == 'release') {
def newName = variant.name + "_" + getCustomVersionName() + "_" + releaseTime + ".apk"
outputFileName = new File(newName)
}
}
}
// 定义一个 task, 属于 build 分组.
Task autoChangeVersionCode = task(autoChangeVersionCode, group: BasePlugin.BUILD_GROUP)
// 修改版本号的 task, 记得 doLast 方法
autoChangeVersionCode.doLast {
Properties versionProperties = new Properties()
versionProperties.load(project.file('version.properties').newInputStream())
def custom = versionProperties.getProperty('custom')
versionProperties.setProperty('custom', ++custom)
versionProperties.store(project.file('version.properties').newOutputStream(), 'increase version code')
}
// 添加任务依赖, 当执行 assembleRelease 任务时, autoChangeVersionCode 任务也会执行.
tasks.whenTaskAdded { theTask ->
if (theTask.name.equals('assembleRelease')) {
theTask.dependsOn autoChangeVersionCode
}
}

NOTE: 需要注意一点的是, 上面的逻辑会先执行 assembleRelease 任务, 然后再去执行版本号加一的任务.

     小结: 深刻的体会到除了官方文档, 其余的参考资料基本上都是坑, 而且时效性很差(这一点也是源于 gradle 没有做老版本的兼容). Gradle 和 Groovy 其实也是 Java, 只不过是对 build.gradle 文件做相应的解析, 生成相应的任务和命令.
    

~感谢捧场,您的支持将鼓励我继续创作~