前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >知乎 Android 客户端 CI/CD 方面的实践

知乎 Android 客户端 CI/CD 方面的实践

作者头像
DevOps时代
发布2019-07-15 19:11:19
2.8K0
发布2019-07-15 19:11:19
举报

前言

伴随着知乎业务的飞速发展,近一年多时间,知乎的 Android 团队由十多人的小团队发展至五十多人的大团队,并且还在不断的壮大中。

虽然我们常常说人多力量大,但是有时候人多也未必是件好事,譬如经典计算机软件著作「人月神话」中就提到在某些情况下 1 + 1 也是有可能小于 2 的。(杜撰的,如有雷同…)

为了让 1+1 大于 2,移动平台团队做了一些工作,不断提升工程师的研发效率,降低各个团队相互干扰,减少重复无用功,支撑业务稳定前行。

下面就就其中 CI/CD 方向跟大家说一下。

组件化方面做的努力

Android 组件化方案 已经运转了近一年半的时间,令人欣喜的是其已经达到了我们当初的预期。即:不同 Android 团队之间,可以通过组件仓库制造代码壁垒,分而治之; 同时其来带的效果也是显著的,即:无论是研发效率还是编译速度都有了不少提升。 但是祸福相依,有得必有失。组件化也不例外,譬如:

  1. 先前代码全在一个仓库,组件化之后,代码跨了多个仓库,代码提交的 CodeReview 很不方便;
  2. 一般修改某个组件的流程是去组件仓库提交代码,合入代码后,发布新的组件包,最后在主工程中使用这个新版组件包,打出测试包。也就是代码测试发生在组件代码在合入后;
  3. 先前单个仓库的时候,主仓库有轮流的 merge 工程师,定期把 release 的代码 merge 到 develop 上面,多个组件后,仓库数量膨胀,如何不依赖组件管理人的细心程度,确保每个仓库的 release 代码能够合入到 develop 之中,也是一个问题。

跨组件的 CodeReview

知乎 Android 端的组件化,是使用如下的文件控制的:

代码语言:javascript
复制
// versions.gradle
component.answer.version = '1.2.3'

主工程读取这些 version 信息,然后再依赖这些组件, 使用类似于这样的语句:

代码语言:javascript
复制
compile com.zhihu.android:answer:${answer.version}

一般而言,我们比如升级了某一个组件,在主工程上面看到的改动就只有类似于这样:

代码语言:javascript
复制
// versions.gradle
+ component.answer.version = '1.2.5'
- component.answer.version = '1.2.3'

至于中间带来什么东西,只能靠工程师自己去翻仓库了,这个很不合理的。

其实由于每个版本都有一个 tag 对应,接着 gitlab 上面已经提供了一个方便浏览的页面

代码语言:javascript
复制
https://git.repo.guest.where.zhihu.com/Android/Ad/compare/<from-commit>...<to-commit>

我们直接输出出来即可。效果如下:

过去没有 tag 的时候,真的是一个个 commit 去搜,现在的孩子真幸福 =v=

联合打包

我们需要一个能够在多个组件提交代码之时,就能打出相应的测试包。

代码语言:javascript
复制
gradle 可以通过下面方法源码依赖某一个工程
include 'moduleA'
project('moduleA').rootDir = '/path/of/module/A'


gradle 版本依赖某一个工程
dependencies {
 implementation 'com.well.zhihu:moduleJoinUs:2.3.3'
}

我们这里是使用一个配置文件,配置需要源码依赖或版本依赖的组件。

如下图:

可以看到这次打包是联合了 ModuleA 以及 ModuleB 组件提交的代码打包。

这里面有个细节,我们在每次开始编译的时候加了「begin to build」以及 job 版本号(图中的 3537),是为了跟最后生成的包的 job 版本号匹配的。

为了让测试的同学知道,这个包是在哪个代码状态下打出来的(打包所获取的组件代码是当前 MergeRequest 提交的代码,担心在打包的过程中,又提交新的代码,这样生成的包就不是当前代码状态的了,让测试同学误解)

分支合并的问题

世界上最冤的 bug 不是字符串的值为 “null”,而是我已经在 release 上修了,但是代码没有合入到 develop。如下图:

bug 是不存在的

如果是 bug 不严重的话,可能就只是浪费测试以及开发资源。但是遇上什么 downtime,紧急修复,忘记合入,则会是新的 downtime,又一次紧急修复。而人总有可能犯错。

知乎这边的做法是定期自动提这样的 merge-request: 「次最新 release => 最新 release」以及「最新 release => develop」(也就是上文的 release-1.2 => release-1.3 release-1.3 => develop )我们会在需要合并的时候定期提醒工程师合并代码,尽量减少工程师的工作。

其他

还有一些细枝末节的,譬如:

有些业务组件的发布流程与主工程同步,在主工程拉分支的时候,也会拉出一个对应的 release 分支,一般自动拉分支的组件都会有自动合并分支的功能。

创建 lint 服务,组件工程只要配置一下,提交代码的时候,都会跑一次 lint,报告贴在 merge request 中,作为 CodeReview 的材料。

包大小监控

业务增长很快带来的另一个问题,是包大小也增长地很快。

包大小减少之前组内 Javascript 工程师 @Peter Porker 做过一次,效果显著,但是无奈,包内增大席卷重来,所以除了直接减少包大小,一套可以无需人为地遏制包大小的增长,或者监控包大小的增长情况的方案,尤为重要。

我们这里做了两件事,一个是使用 gradle + githook 的方式,限制某些不规范的提交(譬如过大的资源文件等),二,实时监控代码提交的时候带来的包增长,生成易读的报告。

限制不规范的提交

不规范的提交包括:资源过大,提交的资源是 png 而不是优化过的 webp,一些低像素的资源也提交过去(-hdpi,-mdpi 现今的设备基本上不会用到这些资源)

githooks 中,可以往 commit-msg 中写一些脚本,检查当前提交的文件内,是否出现上述问题(可以用下列方式获取到当前提交文件: git diff --cached --name-only --diff-filter=d)。

这里有一个问题,git hooks 一般不跟版本走,也就是说很难提交到仓库,然后让别人 down 下来,去覆盖本地的 hooks 文件。想要做到这一点,这就需要外界脚本的帮助。

知乎这边 Android 的开发流程很依赖 gradle,我们的做法是 先把 hooks 里面的所有文件存放在某个仓库里面,然后在 gradle 中植入这些代码:download 这些 hooks 文件,然后覆盖复制到本地的 .git/hooks/ 下。篇幅的问题,代码就不贴了 = =)download 的方法,我是用 git archive 。

最后把这些逻辑写入到 gradle plugin 中。由于所有组件工程都会依赖这个 plugin,这样所有组件工程都会装上 hook,所有的代码的提交都会被你限制到(我给他取名 Ozymandias =v=

安装 git hooks 的效果图:(其实文字都是自己打印出来的,所以 「效果图」谈不上 = =)

美术有点差,见谅 - -

commit-msg 代码检查:

欲擒故纵 =v=

实时监控代码提交,生成相应报告

实际上,这些是不能 cover 住很多情况的,而且有些加入的资源,最后不一定会加入到 apk 包中(譬如 proguard 掉的部分)检查包增长,打个包出来,自然就知道了。

我们这边做的是:

  1. 每次合并代码之后,记录一下最新包的包大小以及包内信息,譬如 develop.detail release-1.2.3.detail
  2. 每次提 merge-request 往 develop/release 合的时候,打一个「假设已经」合入之后的包,获取它的包大小以及包内信息,跟历史纪录对比一下,即可以知道这次改动带来的变化

实现的效果如下:

确实细粒度到类或者包,可能会更好

包内信息我是 unzip 之后,逐一用 du 生成大小以及文件名的信息,交给 python 脚本进行比对的。大致的代码是:(由于文件过多,取最大的前 100 个)

代码语言:javascript
复制
// 统计包大小信息
TOTAL_SIZE=`stat -c %s ${package}`
SIZE_IN_MB=`echo "scale=2;${TOTAL_SIZE} / 1024 / 1024" | bc`
  
  
// 统计包内文件信息
unzip "$package" -d "build/apk"
find build/apk -type f | xargs du -k | sort -n | tail -n 100 \
 | tee "$file_info"
// 生成的包内信息如下:
28  build/apk/res/raw/how.mp3
32  build/apk/res/drawable-night-xxhdpi-v8/are.webp
32  build/apk/res/drawable-xxhdpi-v4/you.webp
  
  
至于对比,只要写个 python 脚本读取该文件,以 name 为 key 的字典即可。

至于对比,只要写个 python 脚本读取该文件,以 name 为 key 的字典即可。

特殊团体的监控

移动平台团队维护的代码,由于调用方过多,稍有不慎,就出问题。所谓不受监督的权力容易滋生问题,所以平台组的成员需要出一套机制监督平台组的运转 ╮(╯_╰)╭

目前的是:

  1. 平台组内的 CodeReview 由另一个平台组成员 + 其他团队人员组成。CodeReviewer 是随机指派的,当然为了CodeReview 效果更好(总不能把做想法的工程师 review 首页的代码 领域不同 CodeReview 效果可能不大好 (@李明亮等100万人: 诶 会有问题吗) ( = =)泥奏凯)这边就是通过看这次改动里面的文件的修改记录(git log), 查到最新的经办人是谁,交给他。
  2. 平台组的代码提交 MergeRequest Open - Merged - Close 事件都会通知到群里面的人 定期每一个迭代都会生成「在这个迭代内平台组的所有提交」的报告,供业务方查看。

就酱. Thanks for reading.

来源:知乎,链接:https://zhuanlan.zhihu.com/p/49542869

近期好文推荐:

DevOps 国际峰会 2019 · 北京站完整实录(附PPT)

程序员自己写测试了,还要测试人员做什么?

Jenkins 中如何实现参数联动构建

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-07-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 DevOps时代 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 组件化方面做的努力
  • 跨组件的 CodeReview
  • 联合打包
  • 分支合并的问题
  • 其他
  • 包大小监控
  • 限制不规范的提交
  • 实时监控代码提交,生成相应报告
  • 特殊团体的监控
相关产品与服务
腾讯云代码分析
腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档