背景介绍
哈啰的业务的多样性体现到APP页面上,我们会发现整个APP的页面设计呈现的方式发生了很大的变化。早些年哈啰APP页面的功能较为单一,上图是近几年哈啰APP的页面,可以看出页面更加多样化,很多业务的功能和信息都在这些页面上展示出来,也有很多交互的能力。
这样的业务发展趋势及APP的页面设计方式,给我们的技术团队带来了两个痛点和挑战。一是交付效率,复合型的页面往往会涉及到多个业务团队的需求,也会涉及到多个技术团队去合作开发,效率就会下降。同时,这些页面都属于流量曝光型页面,产品侧需要做产品的AB测试,尽快地去上线并回收数据。如果发现功能需要调整,产品就希望尽快变更,所以用户触达效率及交付速度都对产品的迭代有很高的要求。二是用户体验,哈啰的首页及各个业务的一级频道页,都是一个业务最核心的流量页面,对用户的体验要求很高,如多端一致性、稳定性和交互流畅度这些指标,相对于其他三四级页面来说,对稳定性的要求也会更高。
技术方案
技术选型的两点思考
面临这些痛点和挑战,我们想到了一些解决方案,首先介绍一下技术选型的思考。一是我们要做一个架构,需要知道架构的能力边界在哪里,要解决怎样的困难和实现怎样的目标。这里列出了现在成熟的客户端APP的技术体系。从服务端来看,主要包括几个能力,最底端的是中间件的能力抽象,上面是业务微服务的能力抽象,再上面是面向端的能力抽象,比如我们通常会做一层BFF来面向端的接口封装,一层CDN来面向端的数据高效访问的能力抽象。从客户端来看,现在成熟的APP基本都有一套自己的组件化框架,做一些通用能力的沉淀,我们想要设计和实现的容器框架就是在组件化框架的上面,它的主要目的是承载各个核心页面的展现和相关的业务逻辑,这是整个容器的能力边界的大致界定。我们希望这个容器首先是标准化的,通过标准化的手段来尽量抹平业务的复杂性带来的问题。二是隔离的虚拟化技术,尽可能提高自身运行的稳定性。这是容器的两个特点,也是我们选定了在这个范围做容器封装的技术。
我们的容器既然要承载整个页面的渲染,应该用什么技术来实现动态化渲染,这是第二个技术选型的思考。其实业内也有很多成熟的技术方案。我们可以从三个维度对这些技术进行归类,一是动态化能力,刚才也提到这是很重要的考虑点。二是原生体验,因为我们对整个页面的流畅度、稳定性都有极高的要求,如果在原生体验上做不到,那这个技术选型就不合适。三是开发成本,我们的成本投入也是有上限的。比如说大家比较熟悉的Web容器的方案,是比较容易引入到整个移动端里的,它的优势就是动态能力,相比整个原生的开发,它天然就具备动态更新的优势。还有像React Native、Weex前几年也是非常火,它的技术选型是在整个原生体验和动态能力做了取舍。DSL方案相对于前面两种又各有优势。我们怎么去做技术选型主要有两个考虑点,包括投入成本和关键收益。一是投入成本,要结合自己业务的状态和团队的人力投入来考量,它需要考虑整个技术研发项目的成本,从头开发还是在已有的架构上做升级,成本是完全不一样的。其次现有系统的升级风险,对现有技术架构兼容性,对业务的影响都需要考虑进去。二是关键收益,任何技术项目都有很多收益,我们要考虑做这个系统,哪些技术能力是主要的,哪些是次要的,哪些是可有可无的。这样分析后我们可以看到为了保证用户体验,性能优先一定是最先考量的关键收益。其次我们要提升效率,尽可能降低团队合作的边界成本。同时我们希望架构可持续地去演进,来满足业务不断增长的诉求。基于这两个考虑,我们最后选择了DSL方案。
架构模型
移动端的核心框架有渲染层、协议层和逻辑层。渲染层是解决静态渲染和动态渲染如何实现的,协议层是提供一些标准化的接口封装,方便模块的运行、通信以及扩展。逻辑层是整个容器框架自己运行的时候,它的状态管理、数据管理、日志管理各方面的一些能力的封装。监控层是整个容器框架自己内建的一套监控体系,我们在这个体系上也搭建了很多可视化的工具和用户触达的能力。模块层更多的就是模块的管理。
动态模块的实现和优化
上图是容器渲染层动态模块的渲染过程。首先还是获取配置,也是刚才整个页面配置里单一模块的配置信息。这个信息里的主要格式有两部分,一是节点信息,决定了这一个渲染素里面每个节点的元素类型,它可能是一个空的容器、一个文本,也可能是一个图片。二是布局信息,我们用Flexbox的语法来描述所有节点之间相互的关系。我们把配置信息解析出来生成了配置树,就是把配置文件变成数据模型。接着是动态绑定,最后会生成一个渲染树。在这个过程中会遇到一些性能问题,如中低端的安卓机,渲染性能较iOS机型会出现明显下降。
我们从多个维度进行了优化工作,一是数据预取;第二是渲染优化,将前置的渲染操作步骤放到后台线程来处理,并铺平页面元素的层级;第三个是数据更新策略的控制,包括版本管理、虚拟节点、动态绑定和Diff算法。
我们把这个卡片上屏之后,用户需要交互能力,于是我们设计了一套action指令的方式来实现,通过预埋在配置信息里的路由命令来实现手势识别和相应的页面跳转操作。action指令能解决80%的场景需求,但在某些场景下不太好用。
比如这样的页面,有一个优惠券可以领,当点击红色去领取按钮的时候,卡片就会发生变化,比如按钮会置灰,卡片的样式也会发生变化。这种场景我们通过实现一套jsruntime来进行支持,通过动态获取脚本来实现类似的操作。
容器逻辑层
这张图是容器逻辑层的模型图,可以从几个维度理解容器逻辑层的运作方式。第一是初始化,容器在启动时进行初始化,整个模块首先要进行自注册,我们在适当时会获取相应的配置数据,接下来会走前面介绍的流程,根据模块的ID做模块的初创建,根据算法来判断是不是要上首屏,然后进行模块上屏。第二是运行时的状态,一个模块运行时主要有四个能力。一是它的布局和样式信息要维持,二是它的业务数据要维持,三是生命周期的状态,包括它的显示、隐藏、创建、销毁,四是事件监听,我们基于一个模块通信的总线来实现整个模块状态的变更和相互关系的关联。此外容器有状态管理的机制。
容器协议层
第三层是容器协议层,我们设计这一层的意义是想让模块开发更加标准化,无论是平台方还是业务方,能降低业务开发的复杂度。我们主要抽象了四层容器协议层,首先容器的布局协议和数据协议相对比较好理解,布局协议解决了布局的一些信息,如模块单元的大小、位置。容器的数据协议就是容器配置数据的填充,如单元格数量和内容;生命周期协议,包括创建、加载、显示、消失和销毁。生命周期协议有很多应用的场景,比如性能监控和曝光计算。事件协议,事件总线的运行是依托事件协议的,比如像模块事件、定时器事件。
容器运行
我们从全局看一下容器运行是怎么做的,这里会看一下前端和服务端做的事情。前端会有一个配置平台。策略中心由算法驱动决定整个模块排序。业务微服务提供显示模块具体业务数据的来源,所有的信息都会融合成一个BFF层。端启动后,比如首页用了容器框架,启动之后它会做容器的初始化、模块的初始化流程,模块运行时又基于事件状态这样的管线来做一些状态的变更和模块的通信。
研发流程的改进
我们设计了整个框架,新的系统对研发、测试、运维的工作方式都会带来很多的变化,很多环节上进行了生产提效。绿色标注的是移动端常规的发版流程,我们看到涉及到的节点和团队都非常多,跨团队的合作会遇到很多问题,发版速度并没有想象那么快。在新的系统下,我们通过在线系统做的变更,这个环节的节点就会少很多,而且更新效率会快很多。
辅助工具
在整个开发过程中,我们也做了很多工具来提升开发的体验和效率。刚才也讲到开发可以去配置平台做配置,我们本地客户端也集成了一个调试工具,会预览渲染的动态卡片的样式。
发布和监控
发布上线的流程不同于传统的客户端发版,它变成了一个在线系统的发布流程。除了发布的变更,我们也做了一些监控分析的能力为它保驾护航,我们会采集性能的数据、稳定性的数据和异常的数据,在这些数据上建立可视化的分析能力和告警触达能力。
项目实践
2020年我们做了首页大改版,整个页面会变成右侧这样。我们希望首页能够承载流量曝光、业务分发、广告营销各方面的能力,首屏的模块会变得非常多,所以整个页面信息的多样化和逻辑的复杂度会高很多。基于这样的背景,我们在2020年做了容器化架构的设计和升级。容器化架构升级主要分为两个阶段,2020年做了跨端统一的客户端的框架,2021年做了容器动态化相关的能力,进一步提升研发效率。我们计划在前后端各个平台,包括运营平台、算法平台等各方面去完善,包括低代码的开发工具,把这个系统做得更好用更高效。
在1.0阶段,我们主要为了支持业务的升级,实现两端统一的容器框架,可以把所有业务都呈现在整个框架上面,同时具备一些基本的标准,比如协议层的协议,还有整个模块容器运行的状态。基于这个事件驱动的模型,2.0阶段我们做了模块化,再之后我们增加了动态化能力,进一步加快交付效率和降低研发成本。3.0阶段我们去完善前后端各系统。
回顾整个项目,我们在复盘的时候总结了两个经验。一是要做取舍,业内也有很多现成的方案可以考虑,但并不是每一个都可以拿过来用,所以做技术取舍非常重要。要结合自己团队的能力和目标来做取舍,并不是越复杂越好。二是我们要持续去做架构的升级,要看长远些,我们的目标是持续推动业务的增长。我们整个首页支持的业务线有10多个,上线之后一年多的时间里迭代了100多次,保持了极高的稳定性,同时团队的研发效率提升30%,技术改造取得了很好的效果。
未来展望
最后介绍一下未来的展望,一是原生SDK可以做的工作,不断提升稳定性,降低崩溃率和异常率,同时逻辑动态化上还有很多可以做的工作。二是完善工具箱,比如IDE、数据可视化等。三是建设生产力平台,让搭建平台更加简单易用,同时要提高系统自动化能力。
(本文作者:秦阳)
本文系哈啰技术团队出品,未经许可,不得进行商业性转载或者使用。非商业目的转载或使用本文内容,敬请注明“内容转载自哈啰技术团队”。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。