作者:张涛
博客:https://kymjs.com/code/2019/11/
大家好,今天跟大家分享的主题是:从小作坊到大工厂,一条电商的 Android 工程化开发实践
我是张涛,就职于一条生活馆,我们公司是一家电商新零售公司,目前移动端的一些开发管理工作都是由我在负责。 对今天的分享有哪些值得探讨的也都欢迎与我交流。
Android 作为一个诞生近十一年的移动操作系统,占据了近 80% 的市场份额。
我相信在座的很多朋友,都是从2013-2015年开始从事 Android 开发的,可能晚一点的有2016年的。这也正是 Android 生态发展最快速的3年,如今我们再回过头来看一下这几年。
这是我们曾经的 APP,携程、美团、京东、淘宝,这是我在网上找的四张图片,时间都是在2014年2015年的,主要是再久一点的也找不到了。
从前我们的应用,这四个应该是很典型了的,因为他们栏目多,业务也多。可以看到,携程四行三列,其他的:美团、京东、淘宝,都是两行四列。
你们都知道,接下来我要放现在的了:这是我最近才从我手机上截的四张图,依然是携程、美团、京东、淘宝。
在这四五年里,互联网公司大幅增加,而这样的老牌互联网公司,新业务也不断增多,我们看到,携程的:WiFi 电话卡、保险签证、以及上面主业务细分更加精细。
美团那就更是如此了,骑单车、叫外卖、美团打车、美团买菜,相信大家都是有所感受的。
再往后京东淘宝我就不用说了,大家也都看得到。
前面从业务角度回顾了我们这几年APP的发展,接下来我们从技术架构角度看一下现代的APP
这张图,是我们一条生活馆的移动架构图,这张图应该可以覆盖如今百分之九十 APP 的架构模型。最上层是业务层,常见的 APP 各个业务模块,比如我们一条生活馆的最重要的几个业务,CMS首页、内容相关模块、电商的商品购物、还有拍卖等等。
第二层,是服务于业务的通用业务层,最常见的浏览器web容器、路由功能。UI 统一、我们的 APP 越做越大,可能一个 APP 由多个设计团队去负责,那设计就需要有一个统一的控件风格。广告,各种闪屏页、内插页弹窗、小红点等等。还有 RN、Weex、Flutter这种多端共用业务。
最下层,是一些基础服务。这里我列出来了8种,大家可以发现一个特点就是这8种都是与用户无关的服务,比如数据上报、异常统计,我作为一个普通的用户完全不在乎我用的应用是否有异常统计,他不会影响我的使用。
侧边是一个贯穿我们整个应用的综合性技术,像 Kotlin 的 Coroutine(协程)、Google 新推出的类似 SwiftUI 的 Jetpack Compose、模块化、国际化、插件化等等,都是改动一块,整个应用可能都会发生变动的技术点。
我们如今的应用已经到了如此惊人的一个庞大体量,但是……
回到我们今天的主题:如何完成作坊到工厂的转变?把上面那些技术全都用一遍吗?哪怕你说插件化 Kotlin 都不适合我们,我找出适合我们的技术都用上,就是大工厂了吗?
不是的,至少我认为不是的。
自从我投身到创业公司以后,经常有人问我大公司和小公司的差别是什么。 在我看来程序员能力的差距都不是最大的问题,当然这是前端,后端可能会因为性能问题造成比较多的损失噢。
在我看来最大的差距就是在基础设施建设上。小公司往往会因为老板的眼界、公司资源等各种各样的原因,缺失一套成体系的工程化平台
那么何谓工程化平台,就是通过标准化流程的方式,去做的能够批量生产的能力。
比如说我们前面介绍的一个应用有多个功能模块,包含基础模块和业务模块,这也是近几年被喊的很火的移动端模块化技术。
第二点,数据驱动模块重组,我们为什么要让这些模块独立是如何选择的。
第三点,我们的研发做好了各个需求,这些需求要如何测试如何上线。
当然,一个前端工程化平台不止这三点能力,比如线上的APM、日志监控等等,只是这三点是我认为作为一个作坊到工厂转变必不可少的三点。
接下来我们从应用架构的角度去一个一个的详细聊聊,首先是业务模块独立。
无论任何一个应用,想要做到模块化拆分,都必不可少要面临这三个问题:跨模块函数调用、跨模块界面跳转、跨模块消息传递。 当然,如果这是一个业界常见问题,一定会有开源的解决方案,比如前面两个,我们可以通过阿里开源的ARouter解决,后一个我相信做Android的没有人不知道EventBus。但是这两个框架在我们真正用起来的时候,都会碰上一些问题。比如ARouter,他从一开始设计就是为了淘宝这样大型的APP去做的,他在应用首次启动的时候要把所有类遍历一遍而这个时间是非常长的,即便是通过分组懒加载也依然很慢。
当然还要一个更重要的原因,是因为我们在ARouter开源之前,就已经有了一套完整的路由框架,所以就没有再使用ARouter了。 另一个EventBus也是订阅者模式都会遇到的问题,就是消息泛滥的情况。当应用内消息过多的时候,会造成消息难以追踪,调试问题的复杂度也随之增大了。
YitBridge 使用了与后端 SOA 设计思路类似的方式:将模块之间的主动依赖倒置,变为功能的提供与使用。 那什么是 SOA 的设计思路呢,我们看到一张我画的漫画图:SOA 它是一种面向服务的架构模型。
例如图上左边有一个对外提供媒体功能的服务提供者,他告知YitBridge我提供媒体服务:“嘿,老铁,我这有个媒体服务,你那边有谁要用的时候可以用我的。”
到了另一边,如果此刻有模块说是,我需要媒体服务:“老铁,你那有没有媒体服务,我这边需要播一个铃声啊!”。
“有的,给你。”
YitBridge 就会将之前服务提供者与服务使用者做一个桥接完成服务的调用。
接下来我们来看具体到代码上是如何使用的:首先是作为服务使用方,也就是上一张图右半部分的Client。我们看到上面是传统的做法,首先声明一个借口类型,然后new出接口的实现类给他赋值。而使用了YitBridge的时候,你是不需要关心接口的实现类到底是谁的。这就是YitBridge唯一的用处,隐藏实现类,做到彻底的面相接口编程。
之前说过,YitBridge将模块之间依赖倒置,由之前的服务提供方被动的接受调用方调用变为,服务方主动提供服务给调用方。那作为服务提供方需要做些什么事呢,非常简单,你只需要给你的对象提供方法加上一个@Creator注解,告诉YitBridge这是一个创建器方法就可以了。
前面讲YitBridge实现完美实现了模块间的解耦,而YitBridge的内部实现就是通过这个APT来完成的。Annotation Processing Tool,相信大家都有听说过这个APT,即便是你没听说过,你也肯定早就用过了,只是你不知道。Android 上有一个注明的注解绑定框架叫 Butterknife,他的内部实现就是通过APT来做的,还有 Google 出品的 Dtabinding、Dagger2,这些也都是APT来做的。那这个注解处理器在 YitBridge 中做了些什么呢,我们来看这段代码。
这是 YitBridge 在编译以后生成的一段类:还是继续我们前面讲的那段示例来继续,在运行时,他会根据你传入的 get()
方法的参数,来判断你所需要的是哪个接口的实现,然后去调用对应的创建器方法。
那么除了这个问题,还有就是多版本协作开发时候的模块依赖问题。Android 使用的是 Gradle 构建,如果出现基础接口更新,上层所有依赖都跟着重新更改一下,再做一次兼容。这种事本来是无可避免的,但是如果我们有多个版本同时开发,这个问题就大大提升了版本管理的复杂度问题。所以我们引入了一个 baseVersion
的概念,也就是你当前模块构建的目标版本号。只要依赖的模块的目标版本号一致,就可以不用考虑兼容性问题直接更新,如果baseVersion
不一致,那说明这是一次不兼容升级,需要上层模块处理完兼容性问题后再发布。
这就是前面 moduleApi
的实现,其实就是判断了一下baseVersion
是否一致,如果一致,那么就直接引用最新版本,如果不一致,则抛异常退出编译。从而将运行时的错误在编译期就直接发现。
第二部分,数据驱动模块重组
数据这里,我能跟大家聊的不多。
第一个,RMF模型。
RFM 模型是电商衡量用户价值和用户创利能力的一个重要数据,分别指三个纬度:最近一次消费 (Recency)、消费频率 (Frequency)、消费金额 (Monetary)。从这三个维度综合评分最终就可以量化出一个用户的价值,那么这个价值就可以辅助我们在线下建立线下店、用户社群等等数据化运营了。
第二个,商品曝光与转化统计。
也是电商公司会经常使用的统计商品转化率的方式,比如一个商品他在用户手机上被展示了多少次,展示了以后用户点击了多少次,这些点击又有多少是收藏或加入购物车了的,以及最终下单购买等等一系列流程,最终就可以分析出每个商品的转化率,慢慢的我们肯定会优先选择跟转化率高的供应商合作。
第三部分,标准化持续集成与交付
这里是我们目前使用的模块依赖构建工具,大家可以从这张图中感受一下。 构建工具,主要的功能是很明显的,就是用于构建模块,在这之上,还有隐含的功能,就是集中了构建模块的权限,可以更便于统一管理; 当然还有最重要的优势就在于模块版本的管理,你可以很清晰的知道当前主应用所接入的模块的版本是哪个,当前最新构建的SNAPSHOT是哪个,以及每个版本的更新日志; 这样做了以后,在跨团队协作上的沟通就大大降低了,如果你已经接入或者即将接入的模块是另一个团队开发的模块组件,那你可以直接关注它,它的所有版本变动日志,最新版本全都一目了然; 并且可以通过平台简化模块的测试与模块发布的流程,比如提测的时候,如果是一次兼容版本的发布,你只需要告诉测试提测分支,测试可以自己根据现在线上应用的tag,同时引入当前提测的模块替换老版本的模块重新编译,很容易就能控制变量。
第二个就是,通过平台化的构建工具,可以很方便的动态选择模块依赖,比如我们在测试某个模块的时候,可以排除部分已经保证没有问题的模块,那么在测试的时候就可以更聚焦,让测试只看我们发生改动了的模块,最终集成测试只需要简单看一下就行了。
接下来我们再看看发布效率。最早我们的代码,是非常严格的 N 周一个迭代的发布。但是随着业务越来越多,团队也越来越大。很多时候不得不 delay 一两天来照顾到所有的业务团队。
之后,就有了我们模块化解耦,每个业务按照自己的迭代排期计划好,如果 delay 了那么这个版本你们的新需求就得放到下个版本再发布了,这样主 APP 的发版不再依赖哪个业务线,同时业务组件可以提前打包,这也加快了打包时所用的时间。
再到最后,通过用户画像精准推送。我们可以做到代码随时发版,只要你认为你的需求做完了,测试通过了,就可以发布 APP。但是这个版本并不一定是每个用户都会接收到,而是我们提前已经分析过用户行为,那些愿意主动使用最新应用的用户才会收到应用更新。大家打开手机里面的微信、QQ 等等,点一下检查更新看看,基本上都是提示已经是最新版本了,但你用的却不一定是最新的版本,你可以去官网再自己下载一个 apk 装上看看版本号。
在之后就是我前面提过的,工厂化发布一个APP。我们可以根据业务需要任意组装业务模块,最后拼成一个APP,快速发布到线上,查看这个应用的数据反馈,如果这个应用从数据层面看运行的非常好,那么就可以考虑引导流量到这个应用。如果数据不好,那么尽早放弃降低研发成本,准备其他的业务也更灵活。
开发者全社区
5T技术资源大放送!包括但不限于:Android,Python,Java,大数据,人工智能,AI等等。关注公众号后回复「2T」,即可免费获取!!