HarmonyOS开发者限时福利来啦!最高10w+现金激励等你拿~ 了解详情
写点什么

知乎 Android 客户端 CI/CD 方面的努力

  • 2020-03-26
  • 本文字数:3378 字

    阅读完需:约 11 分钟

知乎 Android 客户端 CI/CD 方面的努力

前言

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


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


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


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

组件化方面做的努力

Android 组件化方案 已经运转了近一年半的时间,令人欣喜的是其已经达到了我们当初的预期。即:不同 Android 团队之间,可以通过组件仓库制造代码壁垒,分而治之;


同时其来带的效果也是显著的,即:无论是研发效率还是编译速度都有了不少提升。


但是祸福相依,有得必有失。组件化也不例外,譬如:


  1. 先前代码全在一个仓库,组件化之后,代码跨了多个仓库,代码提交的 CodeReview 很不方便

  2. 一般修改某个组件的流程是去组件仓库提交代码,合入代码后,发布新的组件包,最后在主工程中使用这个新版组件包,打出测试包。也就是代码测试发生在组件代码在合入后

  3. 先前单个仓库的时候,主仓库有轮流的 merge 工程师,定期把 release 的代码 merge 到 develop 上面,多个组件后,仓库数量膨胀,如何不依赖组件管理人的细心程度,确保每个仓库的 release 代码能够合入到 develop 之中,也是一个问题。

跨组件的 CodeReview

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


// versions.gradlecomponent.answer.version = '1.2.3'
复制代码


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


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


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


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


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


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


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


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



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

联合打包

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


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 的材料。

包大小监控

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


包大小减少之前组内 Java script 工程师


@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 个)


// 统计包大小信息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.mp332  build/apk/res/drawable-night-xxhdpi-v8/are.webp32  build/apk/res/drawable-xxhdpi-v4/you.webp
复制代码


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

特殊团体的监控

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


目前的是:


平台组内的 CodeReview 由另一个平台组成员 + 其他团队人员组成。CodeReviewer 是随机指派的,当然为了 CodeReview 效果更好(总不能把做想法的工程师 review 首页的代码 领域不同 CodeReview 效果可能不大好 (


  1. @李明亮等100万人: 诶 会有问题吗) ( = =)泥奏凯)这边就是通过看这次改动里面的文件的修改记录(git log), 查到最新的经办人是谁,交给他。

  2. 平台组的代码提交 MergeRequest Open - Merged - Close 事件都会通知到群里面的人

  3. 定期每一个迭代都会生成「在这个迭代内平台组的所有提交」的报告,供业务方查看。


就酱. Thanks for reading.


2020-03-26 19:001136

评论

发布
暂无评论
发现更多内容

【JAVA】TreeSet, LinkedHashSet和HashSet差异对比

笑春风

第八周总结

alpha

极客大学架构师训练营

成为架构师 - 架构师训练营第 04周

陈永龙Vincent

架构师训练营 第四周作业

文江

第八章作业

alpha

极客大学架构师训练营

JVM真香系列:图解垃圾回收器

田维常

JVM 垃圾回收

《Web应用安全权威指南》.pdf

田维常

高交会:高新企业源中瑞在此出展区块链BAAS技术

13530558032

架构师训练营 - 第四周学习总结

joshuamai

作业-第4周

arcyao

极客大学架构师训练营第一期第八周总结

睡不着摇一摇

架构师一期

架构师训练营第 8 周作业

netspecial

极客大学架构师训练营

架构师训练营第 1 期 - 第八周总结

Todd-Lee

极客大学架构师训练营

第八周课后练习

knight

家谱链亮相高交会,点亮“区块链+文化”融合发展之路

13530558032

你以为只是简单的排序?(一)

书旅

数据结构与算法 Go 语言

在线IDE开发入门之从零实现一个在线代码编辑器

徐小夕

Java ide H5 H5制作 代码编辑器

基于f2从零实现移动端可视化编辑器

徐小夕

Java H5 数据可视化 h5编辑器 H5制作

训练营第四周总结

大脸猫

极客大学架构师训练营

【Mycat】Mycat核心开发者带你轻松掌握Mycat路由转发!!

冰河

分布式 微服务 分库分表 中间件 mycat

目标检测-darknet 之模型初始化

Dreamer

作业-第4周总结

arcyao

腾讯强推Redis成长手册!原理+应用+集群+拓展+源码五飞

小Q

Java redis 学习 架构 面试

【可下载】2020年底收官!为大家整理了物联网行业全面研究报告、行业洞察、白皮书……

IoT云工坊

人工智能 大数据 5G 物联网 智能家居

第四周作业

Jack

《身边的金钱心理学》

石云升

面试官最常用小心思:说说你的项目并发量多大?负载均衡怎么做?

小Q

Java Linux 学习 面试 并发

架构师训练营第 8 周学习总结

netspecial

极客大学架构师训练营

python+requests进行get、post方法接口测试

测试人生路

Python 接口测试

“懂行”的价值循环与蝴蝶风暴

脑极体

训练营第四周作业

大脸猫

极客大学架构师训练营

知乎 Android 客户端 CI/CD 方面的努力_文化 & 方法_郑小则_InfoQ精选文章