雪球跨端架构建设之跨端容器篇

4,858 阅读22分钟

作者:雪球大前端团队

导读: 随着移动互联网的迅猛发展,目前市面上「端」的形态多种多样,Web、App 、车载、微信小程序等各种端大行其道,同一个业务需求往往又需要在多端上去实现,针对不同端去编写多套代码的成本显然非常高。近年来「跨端」是前端界比较流行的一个词汇,不论是国内还是国外,跨端技术百家争鸣,方案频出。雪球大前端团队将今年在跨端能力建设上的演进和推广工作整理成系列文章,由七部分组成:

  • 架构全景
  • 跨端容器建设
  • 三端同构建设
  • 高可用建设-运维监控
  • 高可用建设-性能优化
  • 跨端迁移建设
  • 总结展望

本文为第一和第二部分,介绍雪球跨端 容器化 架构的全景以及跨端容器建设上相关工作。

一、背景

一个好的架构是通过不断演变进而去适应业务发展的。雪球在移动端架构演进上,经历了组件化、平台化、H5 混合化的变迁。但是在移动端开发中,仍然会面临如下问题:

  • 客户端发版周期慢、审核周期长,无法做到快速验证、快速上线,影响业务需求发布和迭代效率
  • 客户端需求需要在 iOS 和 Android 双端重复开发,且技术方案难免存在不一致情况,难免因技术割裂产生技术本身导致的人力瓶颈
  • 尽管 Native 技术在用户体验上有绝对的天然优势,但由于工程复杂度高,在部署效率、编译效率上存在天然短板

基于以上问题,今年开始雪球客户端在架构上向跨端技术变迁。过往来看,跨端技术栈虽然在雪球中应用已经有两三年的时间并支持了社区、公募、私募等多条业务线在客户端的接入和尝试,但较长一段时间内受限团队规模一直不大,早期主要聚焦于跨端框架的接入和快速尝试,在能力完善度,尤其是用户体验和周边系统配套方面涉及甚少,因此也没有在团队内部大范围进行推广应用。今年以来,随着业务的迅猛发展,需要跨端技术在大前端全面落地做支撑,因此建设一套完备的客户端跨端技术架构就显得迫在眉睫。

本文将从雪球跨端架构总体概览入手,先简述其全景框架、需要解决的核心问题、以及建设路径,然后重点阐述雪球在跨端架构建设上的具体工作(全文共四篇文章,占用 30-40 分钟,感谢耐心阅读)。

二、架构概览

跨端技术架构的乌托邦,理想情况,在带来业务迭代周期缩短、编译速度加速、开发效率提升的同时,也需要解决多端复用、平台能力、平台支撑等诸多业务问题,这就是雪球跨端架构产生的原因。雪球跨端架构充分利用 RN 和 H5 跨端技术栈来提升开发效率,同时将动态化最大化赋能业务。

雪球在跨端架构上采用了「容器化」的设计思想,如果大家之前对前端跨端技术有所了解的话,相信对于「容器化」这个词并不陌生。所谓的「容器化架构」,就是将前端呈现业务的环境抽象出来,将能力进行标准化,形成统一的容器,通过容器去屏蔽平台和端的差异。容器提供上层标准统一的能力接口,使得业务开发人员专注于容器内的业务逻辑的实现,最大复用已有的能力。换句话说,以雪球现有业务为例,开发者无需关注现在的端是 Android 还是 iOS,平台是社区、基金、还是股票。

2.1 全景架构

如上图所示,整个雪球跨端架构共分为五层:

  • 最底层是系统服务: 因为我们采用了 H5 和 RN 这样跨端的技术栈,使得 iOS 系统和 Android 系统成为了最底层
  • 系统服务之上是公司基于 Native 建设的基建层: 覆盖了研发工程中的基础服务,比如网络框架、平台化基础库等
  • 在基建之上是我们定义的容器层: 包括 RN 容器、WebView 容器、Native 容器等。业务的诉求决定了我们不能选择唯一的技术栈去解决所有问题
  • 再往上,就是垂直的业务层: 包括社区业务、基金业务、股票业务等,每个业务是通过组件化的方式进行拆分。业务都是垂直向下依赖,直接可见容器、可见基建,从而能够更好地通过获取到各种已经建设的能力去完成业务需求
  • 最上面是承载的 APP: 目前有两个 APP,包括雪球 APP 和雪球基金 APP

2.2 解决的问题

雪球跨端架构需要解决以下核心问题:

  • 端和平台一致性: 雪球容器在多个宿主应用之中运行,宿主应用的环境一致性直接影响了容器的一致性。我们的策略是将容器建设成 SDK,通过 SDK 来长期保持容器的一致性,也通过 SDK 内部的设计屏蔽应用之间的差异;对于 Android 和 iOS 端,我们通过分层的设计,尽可能屏蔽端的差异
  • 动态发布能力: 长期以来,客户端同学的开发概念里面只有 App 版本的概念,而当我们逐渐把业务代码做成远端下发时,将会新增一个线上动态发版的概念。当我们在发布业务模块的时候,相对以往的工作,需要额外去考虑这个业务的版本、可以运行的容器对应的 App 上下界版本等。另外,发版的周期也会新增业务模块的发版周期
  • 链路监控: 当我们演进到跨端容器化架构的时候,仅仅关注端的可用性已经远远不能确定业务是可用的了。我们需要从端的可用性延伸出下载链路、加载链路、使用链路上的可用性,针对每个重要的环境,都做好链路监控
  • 性能提升: 针对监控指标进行选取和优化,包括「容器加载速度提升」和「容器稳定性提升」,由此提升跨端容器的可用性
  • 能力推广: 从跨端技术选型、迁移策略、流程机制等方面,探索更多跨端架构接入场景,将跨端架构的动态化能力最大化赋予业务

2.3 建设路径

针对上述问题,今年雪球大前端在跨端技术架构的演进和推广上,分别围绕着「跨端容器建设」、「三端同构建设」、「高可用建设」、「跨端迁移建设」几个方面开展工作,如下图所示:

跨端架构建设.png

跨端容器建设

雪球跨端容器建设上主要包括「 RN 容器」和「 WebView 容器」。之所以选择多个跨端容器,主要是因为单一跨端容器已无法解决我们当下的所有业务问题。除此之外,还通过完善「 JSBridge」和「Native 组件」,弥补跨端容器在部分体验方面的不足。

三端同构建设

跨端容器建设解决了客户端和平台之间的差异是有目共睹的,但是在端的概念上,微信 H5 也是不可忽略的,雪球大前端 FE 团队今年做的一项重要工作就是解决 RN 不支持 Web 端的问题,极大程度上抹平了 Native、web、以及 RN 的技术割裂。

高可用建设

容器化跨端架构带来诸多好处的同时,对线上的可用性、容器的可用性提出了更加严格的要求。雪球大前端通过以下的措施使跨端技术的全面应用变得可控:

  • 通过监控下载、加载等核心链路的可用性,来保障线上动态业务的可用性
  • 通过 crash 治理、加载速度优化等措施保障容器的可用性
  • 通过搭建发布平台,支持降级容灾、ABTest 等策略保障发布的可用性

跨端迁移建设

随着「跨端容器」、「三端同构」、「高可用」等跨端基础设施建设的完善,以及跨端技术在雪球大前端的推广,目前有多个团队同时做页面的跨端技术迁移。为了避免风险、规范流程、提高效率,同时让更多人参与到跨端基础设施建设并沉淀更多基础组件,故需要一个完整的迁移计划方案,从「页面选型」、「迁移策略」、「迁移流程」等多方位来保障跨端技术迁移的有效平稳落地。

2.4 小结

本章节通过全景图阐述雪球容器化架构以及各个层级的职责和联系,随后描述该架构解决的核心问题,最后通过思维导图勾勒出为了实现该容器化架构所做的几个方面的跨端架构建设工作。

接下来我们会针对这几个方面的跨端架构建设工作展开详细阐述,其中本文为第一部分,讲述雪球在跨端容器建设上的主要工作。

三、RN 容器

众所周知,React Native(简称 RN )是 Facebook 开源的跨平台移动应用开发框架,RN 使用 JavaScript 语言和 React 框架来开发 iOS 和 Android 原生的移动应用。RN 容器是基于开源的 React Native 框架改造并完善而成的一套动态化方案。

3.1 为什么选择 RN

RN 已经诞生多年,其社区保持一个很高的活跃度,围绕 RN 产生了许多开发框架。它的思路是最大化地复用前端的生态和 Native 的生态,和 WebView 容器的最大区别在于 View 的渲染体系。React Native 抛弃了低效的浏览器内核渲染,转而使用自己的 DSL 生成中间格式,然后映射到对应的平台,渲染成平台的组件。相比于 WebView 容器,体验上有较明显提升。

总体来讲,跨平台技术有很多,比如 Flutter 也是一个非常不错的跨平台、高性能移动应用框架。结合雪球当下的团队职能以及过往对于 RN 技术栈的积累(Flutter 采用 Dart 语言开发,偏小众语言),同时考虑 Flutter 在热更新方面的局限性,故在跨端容器选型上将 RN 容器列为首选。后面提及的三端同构、高可用建设、以及跨端技术迁移等,也都会重点围绕 RN 展开。

3.2 核心诉求

RN 容器关注的核心诉求是:

  • 动态化能力
  • 双端复用能力
  • 保障措施

在这三个方向上,雪球 RN 容器提供额外功能弥补 RN 基础库的不足:

动态化能力

RN 本身不支持热更新,RN 容器借助公司内的发布平台,实现包的上传发布、下载更新、下线回滚等操作。

双端复用

RN 本身的设计思想是「Learn once,write anywhere」,设计上双端是存在差异的,具体体现在组件和桥上。RN 容器要解决双端(甚至三端)复用问题,从设计原则上要保证开发者对平台的差异无明显感知,为此,需要做以下几件事情:

  • 提供 RN 的基础组件,将常用组件的接口差异进行抹平
  • 将基础设施的 RN 桥,与公司 WebView 容器桥打通,保证桥使用方式上与 WebView 容器保持一致

保障措施

RN 容器如果要大规模使用,还需要提供工具来保障 RN 的开发和运行。

开发保障方面:

  • 提供脚手架工具、模板工程、组件库、开发文档等,方便开发者日常的 RN 开发工作
  • 提供多级分包机制,业务内部和业务之间能够灵活组织代码

运行保障方面:

  • 提供运行环境隔离,使得不同业务之间页面运行无干扰
  • 提供数据采集和指标大盘,及时发现 RN 运行中的问题
  • 提供降级能力应对紧急情况

3.3 全景架构

基于以上思路,整个 RN 容器的系统功能设计如下:

上图展示了雪球 RN 容器所做的事情,分为「框架」和「工具」两个部分,其中,框架部分展示了 RN 容器的定位及边界。

如上所示,RN 容器分为核心和扩展两个部分:

  • RN 容器 基础服务: 包含了针对 RN 的底层改造、以及扩展的基础能力
  • RN 容器 扩展部分: 根据业务公共需求,提供基础能力、UI 组件库、以及 React Native 的桥扩展

四、Web 容器

尽管 RN 可以极大提升客户端开发效率,但是 H5 开发在效率上相比于 RN 更具优势,在一些纯文本展示页、或三方外链页等对性能要求不高的场景下依然是不可替代的。这也是雪球将 WebView 容器作为跨端容器一部分的原因。

所谓 WebView 容器,是 App 统一的 Web 组件,基于原生提供的 WebView 组件,将 WebView 容器化,统一了 WebView 的 UI 展示和交互方式,规范了 JSBridge 协议的使用,同时预置了诸多基础能力和业务能力,从而大大提高 Web 页面的开发效率和用户体验上的一致性。

4.1 方向选型

目前主流的 WebView 容器有两种:

  • 页面为最小支持粒度的容器:比如美团 KNB 容器
  • 业务作为最小支持粒度的容器:比如微信小程序、美团 MMP 等

后者的主要优势是可以提供更广泛的跨端复用性和更严密的业务隔离性,能够支持类似多 App 的业务,同时可以将公司的基础能力以标准化 API 的形式统一对外提供,从而面向三方外部开发者,围绕 App 的开放能力构建出丰富多彩的商业生态,这是普通 WebView 容器所不具备的能力。

虽然类似小程序的 WebView 容器有诸多优势,这里暂且抛开对外开放的部分,从业务角度、平台角度、用户体验、以及当下目标几个方面考虑下雪球是否有必要使用小程序容器:

  • 从雪球自身的业务角度来看:对于业务的隔离性要求不高(目前更多的是做业务融合),这一点还是有别于像美团、京东等多业务 App
  • 平台角度来看:雪球工程作为一个大型复杂的混合应用,场景上 H5 页面大部分是穿插于 Native 页面和 RN 页面之间,这一点也和小程序这种单实例、可保活、多入口的特性存在一定的区别
  • 用户体验来看:用户体验是所有容器都需要关注的重要指标,比如用户主观操作体验、加载性能、稳定性等。由于小程序容器承载的是个多页面的 APP,每个页面都会创建一个 WebView 对页面进行加载。经调研发现,虽然业内主流的小程序容器通过预加载、渲染层&逻辑层分离、同层渲染等手段来提升性能,但启动时间、页面加载时长、FPS 等指标上和页面粒度的 Web 容器相比仍然存在差距
  • 当前目标来看:现阶段团队目标主要聚焦于核心框架和关键能力的开荒建设,在能力完善度、极致用户体验等方面暂时难以兼顾

基于以上考虑,雪球现阶段采用以页面为最小支持粒度的 WebView 容器作为最终选型。

4.2 关键能力

雪球 WebView 容器包含以下几个关键能力:

容器

提供了统一的 UI 展示和自定义样式,例如导航栏样式、页面 Loading 状态等;还有统一的交互方式,例如页面跳转、路由协议解析等。

桥协议

Web 容器虽然在动态化和 Android,iOS 双端复用上很好弥补了 Native 的不足,但在很多体验方面又难以达到 Native 的标准。因此,桥协议(JSBridge) 的出现,定义了 Native 和 JS 通信的标准方式,方便开发时进行桥协议扩展,同时 JSBridge 也内置众多 Native 基础能力,极大地提高了 Web 容器的用户体验和开发效率。

在下文中,我们也会重点介绍雪球在 JSBridge 能力完善上的相关工作。

Web 业务增强能力

预置基础业务页面,例如登录页面、分享弹框等。

H5 离线加载

客户端工程本身会预置 H5 文件(mobile module),每次发版之前进行更新,保证客户端发版时用到最新的 H5 文件。App 启动之后,会提前检查并下载好最新的 H5 文件,这样当用户点击页面入口时,App 直接使用已准备好的 H5 离线文件,仅需加载少量的业务代码,从而达到白屏时间短、加载页面迅速的效果。

异常监控

Web 页面异常情况,获取 console 日志,提交到日志平台,用于排查 Web 内部异常导致的问题。

业务接入

根据各业务的需要,对 bridge 和其他组件与 Native 交互的接口进行统一,接口最终会由各业务层进行具体实现。比如不同业务层需要注册一些特殊的 bridge。

白名单机制

从安全性上考虑,为了避免出现外链通过 JSBridge 获取用户敏感信息,需要对三方链接使用 JSBridge 进行限制。故在配置中心增加白名单配置,未在白名单内配置的 JSBridge 无法被调用,示例如下:

4.3 全景架构

基于以上关键能力,整个 WebView 容器的系统功能设计如下:

Web 容器分为「基础服务」和 「JSBridge 」两个部分:

  • Web 容器的基础服务: 包括离线加载、监控和 Native 的一些功能支持
  • Web 容器的 JSBridge 实际是通过统一 JavaScript 通信规则,实现 Native 和 Web 的交互

业务和 Web 容器平台之间,是「框架适配层」,即统一业务通信的接口和模块的定义。

五、JSBridge

上面提到跨端容器虽然在动态化和 Android/iOS 双端复用上很好弥补了 Native 的不足,但在很多体验方面又难以达到 Native 的标准。因此 JSBridge 在抹平跨端容器和 Native 之间用户体验上起着举足轻重的作用。

5.1 核心能力

雪球通过 JSBridge 内置众多 Native 基础能力,极大地提高了 Web 容器的用户体验和开发效率。今年以来,雪球针对 JSBridge 能力上从平台统一、调用方式、扩展性、业务可定制化、性能等方面进行了全面优化,解决了长久以来 FE 端使用 JSBridge 时候的诸多痛点。其主要工作包括:

  • 调用方式统一: 包括端的统一(Android、iOS)、平台的统一(雪球、基金、雪盈)、容器的统一(RN 容器、Web 容器)
  • 命名唯一化: 将所有 JSBridge 进行集中管理维护,命名唯一化处理的同时增加重名校验机制
  • 相关配套设施完善: 扩展了更多组件,如导航栏、密码框、埋点监控等;同时支持三方 URL 配置 JSBridge 白名单的能力

下面以 Android 端为例(iOS 端原理上大致相同),重点介绍一下雪球在 JSBridge 注册以及调用上的流程机制:

5.2 JSBridge 注册及调用流程

如上所示,应用初始化的时候,调用容器 SDK 初始化方法,在容器 SDK 初始化的同时,注册 JSBridge Handler。调用注册方法有两个参数——方法名和方法实现。注册到本地 Map 集合当中,当 H5 通过统一方法调用原生,需要传入 JSBridge 的名称以及参数,格式为 Json,对应的 JSBridge 名称是 name 字段,收到这个桥接名称后,Native 侧会检查桥接 Map 中是否已经注册过该方法,如果注册过,将参数传入,同时执行对应的桥接方法。方法执行完毕,执行 JavaScript 的回调方法将返回值传给 H5,至此整个通信过程结束。

相应类关系图如下:

可以看到,APP 启动的时候,将注册的 JSBridge 传递给 WebCreator,Creator 将参数传递给 Manager ,Manager 将注册的 JSBridge 放入到 WebParam 这个参数管理类的 Handlers 集合当中。当 Web 接收到 H5 的 JSBridge 请求后,通过持有的 WebParam 遍历其中的 Handlers 集合,找到对应的 Handler,将参数传递,并执行其中的 handle 方法。

六、Native 组件

在抹平跨端容器和 Native 之间用户体验差异上,除了 JSBridge 之外,原生组件也扮演着非常重要的角色。例如:

  • 有时候 App 需要访问平台 API,但跨端容器还没有相应的模块包装
  • 需要复用一些 Java 现有 Native 能力,而不是用 Javascript 重新实现一遍
  • 当你需要实现某些高性能的、多线程的代码,譬如图表绘制、复杂动效、无限列表、或者各种高级扩展等等

我们知道 React Native 本身对这种偏业务和底层调用是不关心的,而 JSBridge 在调用和数据传递上具有一定局限性,这时候原生组件就成为了解决以上问题的首选,我们通过调用原生组件,然后经过特定的封装来达到效果。可以说,原生组件是 JSBridge 能力的补充。

下面列举部分雪球提供给跨端容器的 Native 组件:

组件名称作用
FundCard基金卡片的封装实现,支持在卡片中展示多种图表
Lottie动画组件,支持 RN 上播放通过 JSON 格式导出的 Adobe After Effects 动画文件
Reanimated动画组件,实现在 RN 页面上流畅动画和交互效果
ViewPager原生封装的显示左右滑动界面的控件
LinearGradient原生封装的渐变组件
SNBPullToRefresh下拉刷新组件,用于页面顶部,页面结构最外层,下拉 loading 动画,和客户端下拉动画保持一致
Svg封装了一系列 svg 标签,使 RN 支持 svg 画图
FlashList高性能长列表组件,支持分屏加载和基于 Native 的自动布局纠正
Toasttoast组件,支持正常、成功、失败三种类型、可自定义弹出提示

目前雪球的原生组件系统是基于老架构的 UI Manager 方式,其大体原理如下:

  • 当 App 运行时,React 会执行你的代码并在 JS 中创建一个 ReactElementTree ,基于这棵树渲染器会在 C++ 中创建一个 ReactShadowTree
  • UI Manager 会使用 Shadow Tree 来计算 UI 元素的位置,而一旦 Layout 完成,Shadow Tree 就会被转换为由 Native Elements 组成的 HostViewTree(例如:RN 里的 会变成 Android 中的 ViewGroup 和 iOS 中的 UIView)
  • 线程之间的通信都发生在 Bridge 上,这就意味着需要在传输和数据复制上耗费时间。通过 JSON 格式来传递消息,每次都要经历序列化和反序列化

随着近期 React Native 0.7.0 版本的发布,最大的亮点就是使用一套全新的原生组件系统—— Fabric 组件系统,代替老架构中的 Native Components(详情可参考:Fabric Native Components)。和老架构的主要区别在于 JS 可以直接调用 Native 方法,其实就包括了 UI 方法,所以 JS 和 UI 线程可以同步执行,从而大幅提高列表、跳转、手势处理等操作的性能。随着原生组件库不断完善,我们未来也会在新架构上进行更多探索和尝试。

七、小结

雪球客户端团队通过「 RN 容器」和「 WebView 容器」建设,抹平了端和平台的差异,通过完善「 JSBridge」和「Native 组件」,弥补跨端容器在部分体验方面的不足。但仍不是图灵完备,明年重点方向包括:

  • 精细化运营:目前的跨端容器在精细化运营方面依然存在局限性,我们明年也会调研诸如阿里的 DinamicX、Tangram 等基于 DSL 的动态化框架,向更细粒度容器方向探索,实现类似于动态列表等更精细化运营能力
  • 容器性能提升:JS 的执行效率是当前容器化框架最大的瓶颈,随着 RN 1.0 版本的发布在即,我们也会在新架构上进行更多尝试
  • 能力开放: 在更多接入场景上进行探索,例如搭建 mPaaS 开放平台,将雪球容器框架的基础能力以标准化 API 的形式统一对外提供,从而面向三方外部开发者,围绕 App 的开放能力构建出丰富多彩的商业生态

参考资料

(分享)京东618:RN框架在京东无线端的实践

外卖客户端容器化架构的演进

基于小程序技术栈的微信客户端跨平台实践 | 码农网

Introduction · React Native

本文正在参加「金石计划 . 瓜分6万现金大奖」