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