千万级用户的Android客户端是如何养成的

时间:2021-08-03 16:15:40

Android客户端的架构不论如何演变,架构设计的出发点总是离不开两点,一是提高开发效率,二是降低维护成本。阿刘基于这两点,主要分享了in Android客户端的架构演变。以下是正文:

1.0 时代:小、快、灵
2014年6月份,in发布了第一个版本。到目前为止,已经经历了几十个版本的迭代。在1.0时代,APP的特点是小、快、灵。当时产品逻辑并不复杂,投入的资源不是特别多。因为处于探索期,所以产品的迭代非常快,为了与之适应,in采用了简单的单工程的形式组织整个产品结构,高结构的层次也只有几层,非常浅,如图1所示。

千万级用户的Android客户端是如何养成的

图 1
为了兼容H5跳转,in参考了H5的一种路由协议作为跳转的支持。该时期,in的用户量增长迅速,1.0末期已经达到了近2500万。在这个框架下,in一直衍生到1.9版本,这段时期使用轻量结构比较适合小步快走,即迭代非常快的形式,但这种形式存在一个明显的缺点,即扩展性较差,个别类臃肿,不适合协同开发。随着业务逻辑愈发复杂,参与人员不断增加,如何提高协同开发效率成为当务之急。

2.0时代:繁、稳
in在2.0时代有三个亟需解决的问题:

产品逻辑日趋复杂。复杂表现在两方面:一是演进非常快;二是反复。这是最致命的,因为开发过程中PD可能会临时做一些需求上的变更。因此,此时的框架必须适应产品的复杂化;

代码复用性差。1.0时代,新模块的开发主要基于原有的模块,仅仅在原有的模块上做一个很小的改动,代码实现上却需要进行大幅度的调整;

业务逻辑与基础功能杂糅在一起。因为业务上的一些变更常常会触及底层的东西,导致稳定性不足。

千万级用户的Android客户端是如何养成的

图 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时代:精、稳
千万级用户的Android客户端是如何养成的

图 3
针对2.0时代产品公共类库臃肿的问题,in在框架上做了如图3所示的改进。首先,上层沿用2.0时代的形式,但对公共模块进行拆分,将公共业务抽成代理层,并且引入服务化的概念,将每一个机组功能都抽成独立的服务,比如网络请求、图片上传,本地日志等。这一版改进后,服务都被独立抽出,相互之间是隔离的,每个服务都可以交由不同的人去维护,内部高类聚。

与此同时,每个服务都需要有容错性,每个模块都需要有兜底方案,保证自己的输出是稳定的,自己内部的问题不会影响其他服务。

另外,底层服务很少被上层的业务代码入侵,可尽量通过协议或者是API的形式支持上层的业务逻辑,做到最轻量化级的接入。in还对第三方库做了封装,将其通过代理的模式与自己的业务代码隔离,这样就可以灵活地替换第三方类库,并且大大降低维护成本。

服务化过程中,in沿用了之前的通信模块,并且加入了一个统计框架。这个框架着重突出了服务化的概念,并且是本地服务化。它的优势在于非常的独立,且具有很高的扩展性,每加入一个新的服务,都不会影响到其他的服务,并且在整个架构的层面上来讲,每一个服务之间相互依赖的关系、调用的顺序都可以很快地整理出来。同时,它还给in的产品矩阵打下了一个很好的基础,未来如果推出一些新的APP,需要引用in老的代码时,只需要选择需要接入的那些服务,就能很快理出新的APP的架构图,并且配置起来。

Extra:巧、宜
千万级用户的Android客户端是如何养成的

图 4
图4为in内统计框架,最大的特点是自动化、无侵入式。业界很多统计框架在路径统计层面,主要是统计Activity层以及Fragment层,但是in的很多页面是通过View等其他形式实现的,因此无法通过现有的一些统计框架进行页面统计。对此,in把所有的页面都抽成layer的抽象概念,把所有的layer通过用户的行为路径压到一个layer栈内,最终以一个列表的形式发到服务器,然后在服务器建立一个数据仓库,再通过BI部门整理数据仓库得出每个用户的实际浏览路径,包括每个页面的留存等。

模块内的解耦

千万级用户的Android客户端是如何养成的

图 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机制,它能确保数据先到,页面再打开的时候,数据能顺利下发。

总结
每一套框架都有自己的特点,但是万变不离其宗,最主要的是要适合当前的项目规模和体量,更好的与业务结合在一起,提高开发效率,降低维护成本。

转自:http://geek.csdn.net/news/detail/98761