在上一篇《团贷网Android客户端架构演进之路(上)》中,我们为大家介绍了架构演进、业务重构的过程。在本篇中,我们会从CI出发,在新架构背景下,如何提高研发效率和质量,如何做好架构的维护与复用。
从 CI 到 DevOps
在上篇我们也给大家简单的介绍了一下,CI:持续集成,简单的理解就是将本地编译、构建、发布的过程放在远程服务器多次的自动执行。短短一句话,信息量其实很大。里面有编译构建、发布、远程服务器、多次、自动几个关键词。我们逐个来理解一下:
- 编译构建:通过gradle脚本编译构建出目标文件,如jar文件、aar文件、apk文件等
- 发布:后端程序一般就部署到docker,而我们移动端就发布到nexus,或者上传apk安装包
- 远程服务器:部署了持续集成工具
- 多次:允许多次重复地执行
- 自动:通过某些特定条件触发执行,而不需人手操作
在这几个关键词里面,持续集成工具才是核心所在,我们首先得有一个CI工具,才能做上面说的这些工作,不然都是纸上谈兵。CI工具市面上有很多,比较成熟的如Jenkins、Fastlane、Gitlab、Travis CI等等,我们选用的是最流行的Jenkins作为我们CI平台的核心工具。
CI与基础库
刚开始的时候,我们对Jenkins的理解也只是停留在构建打包的层面,但前面也已经提到,这一步操作也已经为我们的基础库工程发布效率带来了很大的提升。我们通过Gitlab将基础库工程和CI关联起来,当基础库工程完成Merge Request之后,就自动触发CI构建、打包,并自动发布到nexus上,这样为我们测试和发布都带来很大的便捷。
CI与团贷项目
在我们慢慢地对jenkins有了更深入的了解之后,我们又逐步地将团贷项目的构建、打包接入了jenkins,尤其在我们在团贷项目里面加入热修复之后,对分支的管理、基准包的备份和补丁包打包效率这些问题,单靠人手已经远远跟不上节奏,jenkins的接入使得这一些都有了效率和质量上的保证。
DevOps?
其次我们又在jenkins上接入了SonarQube代码质量检测、单元测试、UI测试、app内部分发平台等等。
当我们往这个方向去推进的时候,回过头来发现原来我们所做的已经慢慢地超过了CI的范畴,从需求、代码管理、构建、测试、发布整个链路下来,我们都已经慢慢地往自动化的方向去建设,实际上这是敏捷开发上最近今年比较流行的体系——DevOps。
可能我们经常提及的DevOps就是研发+运维,而更多的也只在后台开发、部署上线,并且结合Docker来讨论的。实际上,我们移动端的DevOps也在不断的发展和完善。能用工具解决的问题,没必要浪费人力成本。将移动端的工作流往DevOps方向建设,不单可以为我们带来质量保证,还有效率的提升。
CI和DevOps将是我们后面重构的一个重心,这里暂时不做多的展开,涉及的点和面都比较多,后面有一篇文章会单独地去详细给大家介绍,讲解团贷网移动端DevOps平台的建设。
新架构的挑战
我们花了大概4个多月的时间,基本上把基础库内80%使用率高的基础设施重构好,而子业务的拆解和重构也在稳定地推进当中,由于每个同事的对新架构的认识存在一定的差异,可能每个模块重构出来的质量和效果都不一,但在保证没有重大Bug的前提下,这些参差我们是可以接受的,毕竟重构不是一朝一夕的事情,在后期我们还需要做深度的优化。
多工程复用
就在我们稳步推进业务重构的时候,我们项目新架构迎来了一个更大的考验——业务模块需要在不同项目之间复用。组件化分层,业务模块化解耦,MVP框架等等这些,很大一方面也正是为了应付这种情况的出现,app之间的模块复用,理论上按照我们设计的架构是很容易实现的。但在我们多次讨论之后,依然还是有几个大的问题需要解决:
- 业务只重构了三分之二,还有三分之一的业务未重构,包括1到2个核心的业务,如投资、投资记录等模块
- 不同项目之间,子业务如何复用?拷贝代码,还是使用Maven依赖?
- 公共业务层如何拆解,如何维护多个项目之间的通用模块和差异模块
实际上,这些问题也是我们重构后期需要思考和解决的,只是没想到这么快就要走上这一步。业务未重构完,只能是加快重构的速度,争取在拆分App上线之前完成提测、合并。而真没法赶上发布的模块,不同项目之间也只能拷贝这部分代码,而这部分代码我们也已经归总到一个独立的Module,只需要复制一份即可。
而已经重构好的子模块,则按照开发新手版的方式增删所依赖的模块。问题在于不同项目工程之间复用的模块是要以源码方式依赖,还是Maven方式依赖,而复用又需要如何体现其差异化的部分呢?
多项目复用架构
经过我们重构小组多次讨论之后,我们基本确定了新的项目架构,如下图所示。
基本上与原来的组件化分层是类似的,为了配合其他项目部分功能的定制化,我们加入了一个子工程-公共业务层,存放其他项目特有的内容,如请求域名管理、ARouter跳转路径、服务实现类等等。而对于团贷网项目,由于CommonBiz本身的抽离就是针对团贷网项目的,所以在时间紧迫的情况下,我们并没有急着增加td_commonbiz层,而放在日后当项目之间差异足够大的前提下,我们再抽离出子工程-公共业务层。
MVP分层复用
对于子业务层的各个业务模块来说,如果要做好两端代码的复用和维护,将公共部分(Common)抽离成独立的子工程是最好的做法,这样能真正做到边界的约束和统一的管理,而差异化的部分则交由两个项目独立维护。实际上这是一种MVP分层复用的结构。
这种分层结构是一种比较优雅的设计,即能达到核心代码的复用,也能解决两端差异化的问题,发挥MVP框架最大的价值。但这种架构模型也存在一定的弊端,就是设计成本比较高,要求开发者对两端的业务都非常熟悉,才能做好公共部分的抽离和差异部分的设计,否则可能差异化部分依然会有一些重复的代码。而这种架构后期的维护成本也比较高,尤其对于两端项目逐渐差异化的发展,可能公共部分的内容会越来越少,最终导致整个架构失控。调试也是一个痛点,将Common部分抽离成一个子工程,这导致一个功能需要在两个工程上面调试,这无疑是会拉低我们研发的效率。
碍于这些问题的存在,我们并没有将Common部分独立为一个子工程,否则我们20个模块,需要维护20个子工程,这是一个不敢想象的事情,起码就目前的团队规模来说还不需要有这样的操作。我们再次调整重构的思路,将子模块进行MVP分层之后,将公共部分源码交由两个项目维护,在观察后期两个项目的发展,再决定是否需要针对个别模块进行统一的复用管理。
父类的定制化
子模块代码复用了,意味着其父类也需要复用,我们两端项目依赖于CommonBiz公共业务层,所以基类复用的问题是可以解决的。但这里存在另外一个问题,基类封装了如标题栏、加载进度框之类的一些UI样式,而我们不同项目对UI设计有不同的要求,主题也不一致。这个问题,我们准备了两个解决方案:
- 修改基类Activity,采用注解或者动态代理方式去定制
- 采用上层资源覆盖的方式,替换默认样式
第一种方案比较灵活,但需要花一定的时间去修改和做好性能测试,毕竟改动的是底层基类。第二种方式比较粗暴,在不修改底层代码的前提下,利用apk编译打包的一个机制——上层同名资源会覆盖底层的资源,做到样式的修改。目前我们大部分样式定制采用的是第二种方案,第一种方案还在调研测试阶段。
现在与未来
说到这里,Android客户端重构的整个技术体系实际过程也介绍得差不多了,这一年时间我们做的可能远远不止上面所说的这些,踩过的坑也还有很多上面没提及的,下面再简单罗列下其他涉猎的部分:
- 引入微信开源的Tinker热修复框架,并对Tinker做深入研究
- 定期组织Code Review会议,共同审核代码、分享经验
- 开发简易版的基础库
- 研究Groovy,通过gradle脚本提升编译速度
- App瘦身研究
- 维护wiki,定期输出原创文章、技术文档
- 适配性研究,如沉浸式状态栏、全面屏适配、原生Api版本兼容性
- Mock服务研究、自动化UI测试研究、云测平台搭建等等…
还有一些我们依然未解决的问题:
- 使用Maven依赖,不利于开发同事在源码和远程仓库之前切换调试
- BI统计对代码侵入比较严重,还没有比较好的封装方案
- MVP分层复用的模式难以推广,还需要寻求一种更优的业务复用方案
- 重构后代码,在质量和性能上的验收和把控
- 多工程联合打包还没完全自动化,在CI建设方面需继续推广落实
最后附上一张我们目前整个Android客户端的技术图谱
回到刚开始说的,这篇文章并不是一个结束,只能说是我们第一次全面重构的总结与分享,后面依然还有很长的路要走,如果在这里能给到同样在这条路上的你一些启发,那是我们的荣幸。后面的路,我们会把重点放在CI、自动化、DevOps建设方向继续推进,同样我们架构的优化、质量与性能的提高是另外的大重点。