编者按:Android 客户端的架构不论如何演变,架构设计的出发点总是离不开两点,一是提高开发效率,二是降低维护成本。5 月 29 日下午,九言科技 inAndroid 客户端的负责人阿刘基于这两点,在七牛云举办的「架构师实践日」沙龙上,为大家带来了题为「inAndroid 客户端的架构演变」的分享。以下是对他演讲内容的整理。
早期从事 WinCE 开发,后转向 Android 平台,在移动开发方面有丰富的经验,目前负责 in Android 客户端架构方面的工作。
1.0 时代:小、快、灵
2014 年 6 月份,in 发布了第一个版本。到目前为止,已经经历了几十个版本的迭代。在 1.0 时代,APP 的特点是小、快、灵。当时产品逻辑并不复杂,投入的资源不是特别多。因为处于探索期,所以产品的迭代非常快,为了与之适应,in 采用了简单的单工程的形式组织整个产品结构,高结构的层次也只有几层,非常浅,如图 1 所示。
为了兼容 H5 跳转,in 参考了 H5 的一种路由协议作为跳转的支持。该时期,in 的用户量增长迅速,1.0 末期已经达到了近 2500 万。在这个框架下,in 一直衍生到 1.9 版本,这段时期使用轻量结构比较适合小步快走,即迭代非常快的形式,但这种形式存在一个明显的缺点,即扩展性较差,个别类臃肿,不适合协同开发。随着业务逻辑愈发复杂,参与人员不断增加,如何提高协同开发效率成为当务之急。
2.0 时代:繁、稳
in 在 2.0 时代有三个亟需解决的问题:
产品逻辑日趋复杂。复杂表现在两方面:一是演进非常快;二是反复。这是最致命的,因为开发过程中 PD 可能会临时做一些需求上的变更。因此,此时的框架必须适应产品的复杂化。
代码复用性差。1.0 时代,新模块的开发主要基于原有的模块,仅仅在原有的模块上做一个很小的改动,代码实现上却需要进行大幅度的调整。
业务逻辑与基础功能杂糅在一起。因为业务上的一些变更常常会触及底层的东西,导致稳定性不足。
基于这三点,in 在 2.0 时代进行了如图 2 所示的重构。
首先,将所有业务分成独立的模块,同时考虑产品逻辑中可能没有想到的模块。以 in 为例,in 有图片详情页,但从产品逻辑看,产品分为个人中心、话题、品牌站等模块,这些模块都有各自的图片详情页,图片详情页并不是一个独立的业务线。初期,in 严格按照产品线去划分业务逻辑模块,所以开发出好几套图片详情页,这就是代码复用性差。经验教训是,模块不应完全由 PD 决定,我们必须非常熟悉产品结构,清楚有没有共性的模块可以单独抽离出来。
其次,业务模块必须具有一定的配置性,即可扩展性。沿用刚才的例子,我们希望个人中心的图片详情页具有显示用户打上去的标签、贴纸等的功能,但品牌详情页可能并不需要这类功能,所以业务模块必须达到可配置。分好业务模块后,各个模块之间相互独立,但必然存在公共的功能,因此需要有一个底层的公共类库做支持。公共类库主要包括了一些基础功能,比如网络请求、图片解析、本地日志系统等。
最后,in 引入了许多第三方功能,而公共模块是一个独立工程。in 允许公共模块直接使用第三方库,但不允许其它模块单独使用。因此,第三方库达到统一管理。同时,in 还沿用并强化了 1.0 时代的路由协议,推送可以通过这套协议跳转到推送页面。
到此,in 基本解决了之前谈到的三个问题,更重要的是提高了 QA 测试效率。
以往需要等所有功能开发完成才能交付给 QA,分模块后,每一个业务模块都可以不依赖其他模块独立运行。当一个模块自己的业务开发完成后,都可直接交付给 QA。但与此同时,产品又产生了新的问题,即公共类库臃肿,难维护,迁移成本高。
2.0 时代,in 的用户量从 1.0 时代的近 2500 万增长到 7000 万。in 意识到每一次小的更新都会影响用户的体验,因此告别了原先快速迭代的发展模式,转为求稳。从开发的角度来说,是要提供更优质的服务。
后2.0时代:精、稳
针对 2.0 时代产品公共类库臃肿的问题,in 在框架上做了如图 3 所示的改进。首先,上层沿用 2.0 时代的形式,但对公共模块进行拆分,将公共业务抽成代理层,并且引入服务化的概念,将每一个机组功能都抽成独立的服务,比如网络请求、图片上传,本地日志等。这一版改进后,服务都被独立抽出,相互之间是隔离的,每个服务都可以交由不同的人去维护,内部高类聚。
与此同时,每个服务都需要有容错性,每个模块都需要有兜底方案,保证自己的输出是稳定的,自己内部的问题不会影响其他服务。
另外,底层服务很少被上层的业务代码入侵,可尽量通过协议或者是 API 的形式支持上层的业务逻辑,做到最轻量化级的接入。in 还对第三方库做了封装,将其通过代理的模式与自己的业务代码隔离,这样就可以灵活地替换第三方类库,并且大大降低维护成本。
服务化过程中,in沿用了之前的通信模块,并且加入了一个统计框架。这个框架着重突出了服务化的概念,并且是本地服务化。它的优势在于非常的独立,且具有很高的扩展性,每加入一个新的服务,都不会影响到其他的服务,并且在整个架构的层面上来讲,每一个服务之间相互依赖的关系、调用的顺序都可以很快地整理出来。同时,它还给in的产品矩阵打下了一个很好的基础,未来如果推出一些新的APP,需要引用in老的代码时,只需要选择需要接入的那些服务,就能很快理出新的APP的架构图,并且配置起来。
Extra: 巧、宜
图 4 为 in 内统计框架,最大的特点是自动化、无侵入式。业界很多统计框架在路径统计层面,主要是统计 Activity 层以及 Fragment 层,但是 in 的很多页面是通过 View 等其他形式实现的,因此无法通过现有的一些统计框架进行页面统计。对此,in 把所有的页面都抽成 layer 的抽象概念,把所有的 layer 通过用户的行为路径压到一个 layer 栈内,最终以一个列表的形式发到服务器,然后在服务器建立一个数据仓库,再通过 BI 部门整理数据仓库得出每个用户的实际浏览路径,包括每个页面的留存等。
模块内的解耦
耦合存在每个模块内。业界很常见的是用 MVC、MVP 等模式进行一定的解耦,in 主要用 MVP 模式。为什么 in 之前 Activity 常常写得特别臃肿?因为它不仅做了 Model 层的事,而且做了表现以及控制上的事。解决办法是把 view 层单独抽离,由 Fragment 去做 View 层的展现,而 Activity 层只专注于对数据的处理,实现 View 层跟 Model 层之间不直接交互,而是通过一个接口的形式进行沟通。
灰度发布机制
灰度发布机制主要是为了支持产品的 A/B Test。in 的产品越来越复杂,用户量越来越多,为了实验性的功能不影响所有的用户体验,只能允许一部分特定用户看到新功能,而这需要通过代码层做控制,即灰度发布机制。如图 5 所示,灰度发布主要在业务层之上,它的配置全部由服务器端决定,确保每个业务都可以做到灰度发布。
模块间通信
模块增加后,模块间通信成为一个大问题,因为模块之间是不可见的。两个解决办法:第一是通过一套反射机制达到每个模块间相对可见;第二是建立一套自己的基于观察者、订阅者模式的消息分发机制,in 的这套机制主要参考了 EventBus 以及谷歌最近开源的一个安卓响应式框架 Agera 类似于 RxAndroid ,这个框架能帮助模块间通信的现成模型的构建。另外,模块间通信有一个Sticky机制,问题就在于当页面未打开之前,数据已经先到了,那么该如何解决?就是通过 Sticky 机制,它能确保数据先到,页面再打开的时候,数据能顺利下发。
总结:
每一套框架都有自己的特点,但是万变不离其宗,最主要的是要适合当前的项目规模和体量,更好的与业务结合在一起,提高开发效率,降低维护成本。
「七牛架构师实践日」——这里只谈架构
七牛架构师实践日是由七牛云发起的线下技术沙龙活动,联合业内资深技术大牛以及各大巨头公司和创业品牌的优秀架构师,致力于为业内开发者、架构师和决策者提供最前沿、最有深度的技术交流平台,帮助大家知悉技术动态,学习经验成果。
七牛架构师实践日第九期【微服务架构最佳实践】将于6 月 18 日与大家在深圳见面,目前活动正在火热报名中,点击这里了解更多信息,期待你的参与。