作者:雪球大前端团队
导读: 随着移动互联网的迅猛发展,目前市面上「端」的形态多种多样,Web、App 、车载、微信小程序等各种端大行其道,同一个业务需求往往又需要在多端上去实现,针对不同端去编写多套代码的成本显然非常高。近年来「跨端」是前端界比较流行的一个词汇,不论是国内还是国外,跨端技术百家争鸣,方案频出。雪球大前端团队将今年在跨端能力建设上的演进和推广工作整理成系列文章,由七部分组成:
- 架构全景
- 跨端容器建设
- 三端同构建设
- 高可用建设-运维监控
- 高可用建设-性能优化
- 跨端迁移建设
- 总结展望
本文为第四到第七部分,介绍今年雪球大前端团队在跨端高可用建设和跨端迁移建设上相关工作,同时回顾今年在跨端架构建设上的主要工作以及未来规划。
一、概述
容器化跨端架构带来诸多好处的同时,对线上的可用性、容器的可用性提出了更加严格的要求。雪球大前端通过以下的措施使跨端技术的全面应用变得可控:
- 通过监控下载、加载等核心链路的可用性,来保障线上动态业务的可用性
- 通过 crash 治理、加载速度优化等措施保障容器的可用性
- 通过搭建发布平台,支持降级容灾、ABTest 等策略保障发布的可用性
下面这张图简要阐述了雪球分别在「事前」、「事中」、「事后」针对跨端技术做的高可用建设:
二、监控运维
雪球监控运维体系共覆盖了「开发交付」、「线上发布」、「线上监控」、「有效应对」、「复盘改进」等五大模块,如下图所示:
开发交付
RD 完成需求开发,提交到 Git 库的发布分支。对应的业务负责人角色进行 Code Review,确认无误之后会执行代码的合并操作。
线上发布
我们通过自研发布平台的 RN Bundle 的发布配置能力去约束发布 App 的版本号、SDK 版本等,从而完成整个发布过程。
线上监控
我们借鉴业内客户端高可用性保障的相关经验,用一些日常运维的手段去发现问题。比如使用 Sentry APM、ELK 日志分析系统、飞书 webhook 等手段从宏观角度主动去发现存在隐患的指标,及时治理,避免问题。
有效应对
在有效应对方面,我们主要靠两种手段。第一种是方案兜底,通过灰度配置和客户端路由实现兜底方案,达到快速恢复成 Native 页面或者 H5 页面继续为用户提供服务的能力;第二种是完善跟进机制,针对告警问题进行了双周 Review,其目的是定期巡视整个前端系统的运行情况,及时发现并解决系统隐患。
复盘改进
在以上几大环节中,问题可能会出现在任意一个环节。除了及时发现问题与解决问题,我们还需要尽力避免问题。这一点主要是靠我们内部的周会、复盘会,对典型问题进行 Review,将问题进行归类,包括流程规范问题、操作失误问题、代码实现问题等,「从场景中来,到场景中去」,力图通过流程和系统的完善更迭不断地提升雪球跨端架构的可用性。
2.1 平台建设
应用程序的稳定性是影响用户体验及留存的关键因素,我们一直希望有个平台能够帮助我们深入了解应用的性能和稳定性、帮助我们高效提升应用的质量。通过实时采集线上版本的性能指标数据,能提供多种辅助定位问题的关键信息,从而能够快速发现问题、定位问题、解决问题。
今年雪球通过搭建 ELK 日志分析系统、Sentry 性能监控平台,自研了一套从端上信息收集到监控告警的全链路监控系统,旨在最大限度保障线上动态业务的可用性。全景图如下:
2.2 性能监控
这里以 RN 为例,客户端加载 RN 主要经过以下几个关键步骤:
如上所示,关键的性能指标包括:Bundle 下载、解压及加载情况、页面流畅性、页面加载时长、crash 、JS 错误、白屏等。为了统计这些关键的性能指标,我们把监控过程拆分为两个阶段——「进入 RN 页面前」和「进入 RN 页面后」:
进入 RN 页面前
将客户端上传的性能指标数据以日志格式存储,借助 Elastic Search 查询,同时存入埋点并联合大数据分析。统计数据可以通过 ELK 日志分析系统看板来查看,如下:
进入 RN 页面后
对于 RN 页面内的 crash、FPS 等指标监控,雪球使用的是 Sentry 性能监控工具。Sentry 是一个基于 Django 构建的现代化的实时事件日志监控、记录和聚合平台,主要用于如何快速的发现故障。支持几乎所有主流开发语言和平台,并提供了现代化 UI,它专门用于监视错误和提取执行适当的事后操作所需的所有信息,而无需使用标准用户反馈循环的任何麻烦。
雪球通过阿里云资源服务器私有化部署了一套 2022 年最新版本的 Sentry,样式预览如下:
2.3 行为日志采集
针对用户行为,雪球也封装了一套日志组件,提供收集用户行为日志的功能;同时支持通过长链接下发指令在线捞取客户端日志。整体框架如下:
如上所示,客户端在收到 JSBridge 调用后,在 message 前面加上「React Native log =」或者「 H5 log = 」特殊的字符串标识,来区分日志是来自 Native 还是 React Native ,亦或是 H5 页面。
我们当前通过「主动上传」和「静默回捞」两种方式进行日志采集,具体包括:
- 用户通过 APP 设置页功能主动上传日志
- 通过 CRM 配置自定义规则,满足一定规则会自动上传日志,比如目前雪球日志采样率是 10%
- 通过 WebSocket 长链接下发指令,实现静默在线回捞日志
2.4 数据告警
雪球通过配置飞书机器人实现监控平台和飞书群的联动,从而达到监控数据达到阈值之后的自动告警和开发的快速跟进,大大提升了大前端的协作效率,全链路告警机制如下图所示:
2.5 跟进机制
一套全链路的监控运维机制,除了完备的监控告警系统之外,问题的跟进也是链路闭环中重要组成部分。目前雪球针对告警问题进行了双周 Review 机制,其目的是定期巡视整个前端系统的运行情况,及时发现并解决系统隐患。
大体流程是针对告警问题进行采集,主要包括关键 bug、crash、性能瓶颈点等,同时进行梳理、聚合、指派,其间各个问题负责人需要对问题进行整体跟进,问题解决后,根据问题的严重程度评估是否再次发版。流程图如下:
2.6 发布降级
对于一套健全的跨端技术框架,完善的发布能力是不可或缺的。这里主要依赖于雪球自研的发布平台和客户端路由系统。
发布平台
雪球通过自研发布平台完成整个 RN 发布过程,核心步骤包括发布准备(Git 拉代码、环境参数确认、本次发布说明填写)、发布自检(依赖问题检查、Lint、单元测试)、正式打包(Build、版本号自更新)、产物上传测试环境(测试/线上环境隔离、测试环境进行测试),双重确认(QA、Leader 确认发布)、产物上传线上环境等。
以 RN 为例,在发布平台上,我们可以借助 RN Bundle 的发布配置去约束发布 App 的版本号、SDK 版本等,以及具体要发布的业务 Bundle 名称,从而满足我们不同的发布需求。最终执行发布操作,将 RN Bundle 上传到 CDN 服务器,供用户下载,完成整个发布流程。
详细的部署发布上线流程如下图所示:
路由降级
即便我们在发布管控和线上监控上做的再充分,线上问题最终还是无法避免的。所以当发现线上问题之后,我们需要及时的应对问题、解决问题,把问题带来的影响降低到最小,并以最快的速度恢复对用户的服务。这里我们主要依靠路由降级方案,将需要降级的页面配置在 crm 后台,包括页面的地址(url)和降级后页面打开类型(openType)等信息,客户端启动时会拉取该配置信息。以 RN 为例,短时间内切换成 H5 或 Native 页面继续为用户提供服务,同时通知相关 RD 和 QA 快速定位问题,及时修复、验证并上线。通过这种备案保障雪球客户端使用跨端技术时业务的整体可用性。
雪球当前路由解析策略如下图所示:
2.7 小结
虽然雪球大前端跨端监控运维系统在今年实现落地,并且服务多个应用,但仍不是图灵完备,未来也会继续从开发交付、线上发布、线上监控、有效应对、复盘改进这五大模块持续完善和迭代。
三、加载速度优化
上面阐述了雪球高可用建设上运维监控相关工作,在本文后面章节中将继续介绍雪球今年在跨端高可用建设上的另一个重点方向——容器性能优化。整体优化思路上分为两个大的方向:「容器加载速度」和「容器稳定性」:
容器化跨端架构的演进加速了雪球跨端容器的基础设施建设,顺利支持了例如「私募标的」、「基金档案」等大体量复杂模块的接入。随着跨端容器基础设施的逐步完善和跨端技术的大规模应用,性能质量体验提升成为了我们另一个重点关注的方向,其中对于客户端来讲关注最多的就是 ****RN 容器 的加载速度。
和原生开发相比,React Native 比较明显的不足在于页面加载速度慢,比如秒开率、页面加载时长等。因此针对 RN 容器加载速度的优化,是性能优化的重中之重。
3.1 现状梳理
在开始页面加载优化之前,我们首先统计了之前雪球客户端的性能瓶颈。前面讨论运维监控时候提到过 RN 容器加载过程中的几个关键节点,可以说加载速度就是取决于这些关键节点的时长。下面通过一张更为详细的流程图阐述 RN 容器加载全过程和链路指标之间的关系:
可以看出,一个 RN 页面的主要加载流程包括:版本请求、Bundle 下载、React Native 框架 Native 部分的初始化、React Native 框架 JS Bundle 的初始化、业务的数据请求、以及最终的业务渲染。
这里我们可以继续归纳为三个大的阶段:热更新阶段、Native 加载阶段、JS 执行阶段。以 iOS 端为例,结合上文提到的 ELK 日志分析系统和 Sentry 性能监控平台提供的数据,雪球未优化前在这三个阶段的平均耗时和占比如下:
| 阶段 | 耗时(ms) | 占比 |
|---|---|---|
| 热更新 | 2700(P75) | 69% |
| Native 加载 | 500 (P75) | 13% |
| JS 执行 | 700 (P95) | 18% |
| 总计 | 3900 | -- |
如上所示,如果不考虑任何优化的情况下首次打开一个 RN 页面的时长,就是上面三个阶段的总时长。可以看出,首次进入一个 RN 页面耗时将近 4 秒的时间。
3.2 整体方案
上面提到的 RN 容器加载不同阶段,除了 JS 执行阶段之外,其他阶段客户端都有相应的一些手段进行专项优化。通过结合雪球工程和业务现状,同时参考业内相关最佳实践,重要优化手段和页面加载关系如下图所示:
下面详细描述几个重点优化手段的具体方案。
3.3 Bundle 热更新 & Bundle 加载优化
雪球之前的热更新策略是仅在应用冷启动阶段进行一次热更新,且需要在第二次启动时候才生效。但这样无法保证页面级别的热更新,一旦出现一些较严重的 bug,会出现更新不及时的情况,影响用户体验甚至转化率等。
为了保证页面级别的热更新,需要每次进入 RN 容器后检查最新的 Bundle 版本,若有更新,就要下载最新的 Bundle 并通过 RN 引擎加载 Bundle。在这个过程中,会经历两次网络请求,如果用户网络环境较差,那么从进入页面到开始渲染内容需要等待很长时间。
考虑到更新频率和页面加载时长之间的平衡(既可以满足类似页面级别的热更新,又要兼顾用户体验),我们采用异步更新 Bundle 包的策略:
如上所示,异步更新 Bundle 包的主要思路如下:
- 在进入页面之前提前下载 Bundle 包
- 进入容器页后先检查是否有缓存 Bundle 包,如果有我们就优先加载缓存并渲染
- 同时异步检测是否有最新版本的 Bundle 包,如果有下载到本地并更新缓存
- 下次进入容器页时加载最新缓存的 Bundle 包,使其更新生效
可以看出,在应用启动时,我们对 Bundle 包提前进行下载并缓存,用户跳转到 RN 容器页后,先检测本地是否有缓存的 Bundle 包,如果有就直接加载并渲染页面。这样就不会被版本号检测网络请求以及下载最新的 Bundle 网络请求所阻塞,同时也不依赖于用户网络情况,大大减少了用户进入 RN 页面的等待时间。在页面渲染的同时,通过异步线程检测 Bundle 的版本,如果有新版本就进行更新并缓存,下次进入时生效。
另外在 Bundle 加载策略上,结合雪球具体业务情况,采用「引擎复用策略」和「引擎预加载策略」,大体原则就是同一个业务 Bundle 共用同一个 RN 引擎 ,应用冷启动时预创建主 Bundle(雪球 Bundle)的 RN 引擎。具体流程如下:
从上图中可以看出,雪球的 RN 引擎创建策略是:
- APP 冷启动时创建社区 Bundle 的 RN 引擎,并缓存在应用内存中
- 打开社区 RN 页面时,使用预创建的 RN 引擎进行页面加载渲染
- 第一次打开基金 RN 页面时,初始化基金 Bundle 的 RN 引擎,使用其进行页面加载渲染,同时将其缓存到应用内存中
- 再次打开社区 RN 页面或基金 RN 页面时,根据索引找到对应的缓存的 RN 引擎进行页面加载渲染
这里我们还需要考虑线程同步的问题,因为雪球 APP 中会有多个 RN 容器,所以会存在线程安全问题,有可能出现并发创建多个 RN 引擎的情况,所以在做 RN 容器缓存时,需要使用线程安全的数据结构。
下面通过流程图详细阐述雪球的 Bundle 包下载、 Bundle 刷新、以及引擎缓存的完整逻辑:
3.4 引擎升级
Facebook 在 ChainReact 2019 大会上正式推出了新一代 JavaScript 执行引擎 Hermes。Hermes 是个轻量级的 JavaScript 引擎,专门对移动端上运行 ReactNative 进行了优化,Hermes 可执行字节码,也可以执行 JavaScript。官方给出了 Hermes 引擎的一组数据:从页面启动到用户可操作的时间长短(Time To Interact:TTI),从 4.3s 减少到 2.01s;App 的下载大小,从 41MB 减少到 22MB;内存占用,从 185MB 减少到 136MB。
基于以上考虑,将 RN 引擎替换为 Hermes 便作为了容器加载速度优化的一个重要技术手段,同时伴随着 Hermes 引擎的替换,RN 基础库也需要做全面升级,从某种意义上来讲,这也是在未来业务侧大规模使用 RN 技术应用之前的技术储备和演进,可以很好避免未来积累更多技术负债,同时也能享受到最新框架带来的技术红利。
关于 Hermes 是如何优化的,网上有不少相关资料可供参考,这里概述下最重要的两点优化:
字节码预编译
现代主流的 JavaScript 引擎在执行一段 JS 代码的大概流程是:
- 先读取源码文件
- 解析源代码并转换成字节码(bytecode)
- 最后执行
在运行时解析源码转换字节码是一种时间浪费,所以 Hermes 选择预编译的方式在编译期间生成字节码。这样做一方面避免了不必要的转换时间,另一方面多出的时间可以用来优化字节码,从而提高执行效率。从官网上截取的相关示意图如下:
放弃 JIT
为了加快执行效率,现在主流的 JavaScript 引擎都会使用一个 JIT 编译器在运行时通过转换成机器码的方式优化 JS 代码。Faceback 团队认为 JIT 编译器有两个主要问题:
- 要在启动时候预热,对启动时间有影响
- 会增加引擎 size 大小和运行时内存消耗
基于这两点对性能指标的影响,Faceback 团队决定不实现 JIT 编译器。
这里所谓放弃 JIT,有两点需要再解释一下:
- 纯文本 JS 代码执行效率降低: 放弃 JIT,是指放弃运行时 Hermes 引擎对纯文本 JS 代码的编译优化。相关验证数据也表明,纯文本的 JS 代码执行,Hermes 引擎明显比 JavaScriptCore 慢
- 对 RN 代码的动态性无影响: 由于 Hermes 仍然可以执行纯文本的 JS 代码,并且可以支持动态读取bytecode, 因此对 RN 的动态性并无影响。
升级策略
关于如何升级为 Hermes 引擎,我们主要参考 React Native 官方文档,将 RN 版本从 0.61.4 升级至 0.68.3。
另外说一点,RN 在 0.70.0 版本开始支持默认使用 Hermes 引擎,但是 0.70.0 版本当时刚刚发布,部分第三方 RN 组件仍存在兼容性问题,从风险角度考虑,故没有将其升级到最新版。但是从 RN 的版本趋势不难看出,Hermes 引擎已然作为其官方主推的 JavaScript 引擎,未来的某个时间点,雪球也会将 RN 版本做进一步升级。
3.5 Bundle 包拆分
我们知道,雪球 APP 中的 Bundle 包是热更新平台根据版本号进行下发的,每次有业务改动,我们都需要通过网络请求更新 Bundle 包。当前雪球 APP 的 Bundle 拆包方案是按照大的业务线来划分的(雪球、基金、雪盈)。不过,其实只要 React Native 底层库版本没有发生变化,Bundle 包中的基础库源码部分是不会发生变化的。
包内容分析
FE 团队首先对雪球 Bundle 包内容进行分析,如下图:
可以看到,在雪球 Bundle 中,基础库几乎占整个包大小的 80% 以上,这无疑增加了 Bundle 下载解压、以及引擎初始化和加载 Bundle 的时长。
因此,我们今年下半年对雪球 APP 现有的多 Bundle 方案进行进一步优化,将一个 Bundle 包拆分成两部分:
- 一个是 common 包: 也就是 RN 底层依赖和基础库源码部分,这部分除非底层库或者官方版本升级,否则几乎不会发生变化,内置在工程本地
- 另一个是业务代码包: 也就是我们需要动态下发的部分
除此之外,雪球 FE 团队还调研支持多 Bundle 的包内容可视化工具,方便开发者对于 Bundle 内容进行清晰的查看与分析,目前尚在开发阶段,其效果如下:
拆包
关于如何拆包,雪球 FE 团队通过自研脚本实现的,大体原理是基于模块进行拆包,和基于文本拆包的主要区别是 common 包和业务代码包可以分别独立执行。这样做的好处是即便业务包没有下载完成,内置在客户端的 common 包也能提前运行,既提高了首次加载 RN 页面的速度,又提升了 RN 容器的鲁棒性(不会因业务包下载失败导致 RN 模块整体不可用)。
拆包具体实现方式较为复杂,这里不再展开。大家有兴趣可以参考 metro 拆包相关资料(metro 拆包工具)。
合包
除了拆包之外,想要展示完整正确的 RN 界面,还需要做到「合」,这个「合」就是指在客户端的分步加载。总体分为两步:
- 加载 common Bundle
- common Bundle 加载完成后加载 business Bundle,即用 common + business Bundle 初始化一个完整的 RN 引擎
详细流程如下:
通过 common 拆包,目前雪球减少了 80% 以上业务包体积,P75 下 Bundle 包下载时长从 2s 缩短到 0.8s ,解压时长从 0.5s 缩短到 0.3s,极大地提升了 Bundle 热更新可用性。
后续计划
虽然目前的拆包方案,已经减少了业务 Bundle 的大小,但是雪球目前的业务非常庞大,以当前的拆分颗粒度来看,尤其是雪球业务 Bundle,未来势必会愈加庞大。随着 RN 在工程中的全面应用,未来还是存在热更新速度变慢的隐患。
针对这个问题,明年我们计划对业务 Bundle 进行再次拆分,将一个业务 Bundle 拆分为一个主 Bundle 和多个子 Bundle 的方式。比如把一些相对不太重要的页面(层级比较深或 PV 较低的页面),作为子 Bundle 拆出来。在打开雪球 APP 的时候,优先请求主 Bundle 包,达到快速渲染首页中相关 RN 页面的效果,当用户打开某个低 PV 的三/四层级页面时,再继续下载对应模块的 Bundle 进行渲染,从而进一步减少 Bundle 的下载和加载时间。类似下图所示:
优化效果
通过优化,雪球客户端的 RN 容器加载速度较之前有了明显提升,具体优化效果如下:
| 阶段 | 优化前(ms) | 优化后(ms) | 提升百分比 |
|---|---|---|---|
| 热更新 | 2700 | 700 | 75% |
| Bundle 加载 | 250 | 175 | 30% |
| 页面渲染 | 700 | 500 | 28% |
从表中可以看到,在支持页面级热更新的同时,各个阶段的总耗时从 3650ms 降低至 1375ms,速度提升 60% 以上,核心链路的转化率也有了相应的提升。
四、稳定性优化
在性能优化上,除了聚焦于容器加载速度之外,容器的稳定性也是至关重要的。在容器稳定性方面,我们依据 Sentry、Bugly 等性能监控平台,对容器相关的历史性疑难杂症和 crash 进行专项优化,甚至解决了一些行业内都比较棘手的疑难问题。
4.1 疑难问题
上文提到过告警问题采集主要包括关键 bug、crash、性能瓶颈点等。针对这些疑难问题,雪球前端团队在解决过程中体现了追根溯源、精益求精的探索精神,以下面两个问题为例:
RN-useEffect 中接口请求失效
如果开发过 RN 的话,相信大家对于 useEffect 接口并不陌生,useEffect 意思是用于处理组件中的副作用,用来取代生命周期函数,其用法是指定一个副效应函数,组件每渲染一次,该函数就自动执行一次。RN 的网络请求大多在 useEffect 这个函数中执行,这已然成为业内普遍认可的最佳实践。
但是在 Android 手机上,会偶现 useEffect 中的代码没有执行的情况,通过在活跃的开发者社区咨询交流,发现业内普遍解决方案是在 useEffect 的接口请求处增加 300-500ms 的延迟,由此来解决因为请求不执行而导致的白屏问题。但这种方式对于一个追求极致体验和稳定性的 APP 来讲,仍是饮鸠止渴。
雪球客户端团队通过在 RN 和 Android 通信的链路上补充大量日志,多次尝试复现此问题,同时结合 RN 与 Android 通信源码,最终发现 RN 与 Android 通信的桥接层有判断某个对象为空就中断请求的逻辑,从而发现产生问题的根本原因。如下:
当 currentActivity 为 null 时,导致,RN 发送过来的信息都不能执行。
找到产生问题的原因后,接下来雪球客户端团队针对 RN 底层框架做进一步分析得知,RN 与 Android 通信的入口处使用的 currentActivity 有两个目的:
- 调用 Activity 中的 startActivity 方法
- 根据当前的 activity(SnowballReactNativeActivity ) 取出里面的 Fragment(SnowballReactFragment)
在 bridge 执行时页面(SnowballReactNativeActivity)已经打开, Fragment 中 onCreateView 已经执行, 所以当前的 activity 一定是存在且不会为空;所以这里没有必要用 RN 框架里面的 Activity,它初始化时机较晚,我们可以自己实现去获取当前 Activity 的逻辑,代码如下:
同时,AppBaseActivity 的 getTopActivity 用到的 sTopActivityWeakReference 初始化时机从 onResume 改为 onCreate,由此问题得到解决,代码如下:
ViewManager for tag XXX could not be found
此 crash 问题由于日志信息都是源码框架抛出的,且极难复现,导致一直找不到出错原因。客户端团队很好的利用了 Sentry 监控平台进行日志分析,通过仔细观察日志,发现有退后台的操作提示,再加上日志路径中出现的销毁当前页面紧接着重建当前页面的路径信息,进而推断出是:切后台触发页面销毁又重建的流程,然后尝试 Android 的分屏功能,稳定复现此问题。
定位出问题的代码后,又把 RN 通信框架流程详细梳理了一遍,最终找出了为什么分屏操作导致崩溃的真正详细原因。总结的分屏操作后 RN 框架实例创建和销毁执行过程时序图如下:
4.2 经验总结
除了上面提到的问题之外,雪球大前端团队今年还解决了例如「非父子关系页面数据传递」、「前后台切换页面监听」、「软键盘兼容」、「网络请求通用错误处理」、「线程竞争崩溃问题」、「路由失效」、「内存抖动」等诸多棘手问题。通过疑难问题的解决,雪球大前端团队不仅对 RN 底层框架有了更深入了解,同时也沉淀出很多问题处理的方法论。例如:
- 针对 crash 问题:总结出如何快速通过性能监控平台去定位影响范围、复现问题的方法
- 在源码分析上:总结出如何借助搜索工具快速定位到关键代码的最佳实践
- 代码调试、内存调优等方面:通过排查问题总结出相关针对不同问题使用不同调试工具和手段的最佳实践
- 通过这些经验的积累,雪球客户端团队在提升容器的稳定性方面愈加精进。
今年通过对 RN 容器稳定性的优化,目前已经解决了数十个疑难 bug 和 crash,客户端 RN crash 率已经降低到 0.2% 以下。可以说稳定性的提升相当于给雪球大前端注入一针有力的强心剂,极大增强了团队大规模进行跨端技术推广的信心。
4.3 小结
跨端容器的高可用建设是做跨端技术推广的基石,从链路监控到发布降级,再到性能调优的全过程,每一环都至关重要,这里面涉及的无论是技术广度还是技术深度都纷繁复杂,我们从今年开始搭建这些基础设施建设,目前仍处在初级阶段,未来仍离不开团队成员的出谋献策、不断深挖。
五、跨端迁移
随着「跨端容器」、「三端同构」、「高可用」等跨端基础设施建设的完善,以及跨端技术在雪球大前端的推广,目前有多个团队同时做页面的跨端技术迁移。为了避免风险、规范流程、提高效率,同时让更多人参与到跨端基础设施建设并沉淀更多基础组件,故需要一个完整的迁移计划方案,从「页面选型」、「迁移策略」、「迁移流程」等多方位系统性地保障跨端技术迁移的有效平稳落地。
5.1 页面选型
除了 Native 之外,目前雪球存在两种跨端技术栈—— RN 和 H5,面对业务持续增长和安装包不断变大的压力,选择合适的技术栈显得尤为重要。H5 在性能和用户体验方面相比 Native 和基于 Native 渲染的 RN 弱一些,所以目前大部分 H5 页面只是用来承载需求变更频繁、需要即时上线的活动页面。那么 RN 和 Native 的界限是怎么样的,当有一个新的页面产生时,我们应该如何做取舍?这里雪球大前端团队根据经验定义了一套选型规则,如下:
| 类型 | 细类 | 举例 | Native | RN/H5 同构 | H5 |
|---|---|---|---|---|---|
| 展示类 | 静态展示类 | 列表、纯展示 | ❌ | ✅ | ❌ |
| 简单交互类 | 交互简单、业务复杂 | ❌ | ✅ | ❌ | |
| 千人千面 | -- | ❌ | ✅ | ❌ | |
| 交互类 | 复杂交互功能 | 键盘、嵌套滚动、频繁精细滑动、滑动冲突 | ✅ | ❌ | ❌ |
| 一般动画 | 轮播图、弹框、进度条等 | ❌ | ✅ | ❌ | |
| 精细动画 | 下拉刷新、加载动画等 | ✅ | ❌ | 兼容性差 | |
| 组件类 | 通用业务组件 | 相册 | ❌ | ✅ | ❌ |
| 页面内多个 Native 组件 | 公用部分不多、大部分是 Native 组件堆砌的 | ✅ | ❌ | 不支持 | |
| 原生能力组件 | 图片选择、人脸识别等 | ✅ | ❌ | 不支持 | |
| 全局模块 | 高性能模块 | 手势动画、拖拽等 | ✅ | ❌ | 兼容性差 |
| 强交互模块 | -- | ✅ | ❌ | 兼容性差 | |
| 外链 | 需要外链展示 | 活动、广告等 | 不支持 | ✅ | ✅ |
5.2 迁移策略
技术实现角度
从上面页面选型可以看出,除外链展示页以外,其他页面不推荐/不支持使用 H5 开发,因此优先把当前页面中用 H5 实现的使用 RN 重构迁移。
业务链路角度
以基金业务为例,从整个业务链路来看,考虑到中间页面包括承上启下的责任,逻辑较为复杂,因此从尾部页面(投后)或头部页面(投前)逐步向中间页面(投中)迁移,例如交易工具为中部页面,逻辑较为复杂,且对于用户的影响巨大,不建议短期内做 RN 迁移,可先从持仓页或货架页开始迁移。
业务类型角度
以基金业务为例,主要包括投顾、公募、私募、社区等模块。社区业务较为简单,新增的页面优先进行 RN 开发;私募页大部分为 H5/RN 开发,Native 页面涉及的较少,考虑到迁移的风险较低,也可以优先替换私募页 H5 部分页面;投顾业务较为独立,同时和其他业务耦合度较低,建议可以从投顾头部页面(例如货架页)开始迁移;证券、股票、行情等业务较为复杂,迁移的优先级最低。
页面类型角度
强交互(同时存在2种及以上手势操作)、包含复杂动效、或对用户体验要求极致的页面,类似首页,不建议做跨端技术迁移;对于静态展示类,或简单交互类型页面,考虑需要兼顾动态化和用户体验,建议 RN 迁移;外链展示的页面,比如向外投放活动的运营页面,对于性能要求不高,考虑 H5 开发效率和灵活性更高,不建议做 RN 迁移。
综上,以雪球基金业务为例,按照迁移策略,整体迁移计划如下:
当然迁移策略只是给我们一个可参考的 Checklist,但这不意味着墨守成规,很多时候我们还要根据特定场景做特定分析,做到随机应变,甚至在一个页面内包含多种容器。比如在做私募商品页的迁移时,考虑到商品信息展示上对于动态化要求较高,同时还要兼顾复杂的底部讨论浮层联动效果,故使用了 RN 和 Native 混合容器的方式完成页面重构,从而达到「取百家之长补一己之短」的目的。
5.3 流程机制
所谓机制,就是设计一套方案,来专门应对某个场景出现的问题,指导我们做好这类工作。在做跨端迁移计划时,通过总结并提供标准化流程,可以帮助团队决策迁移页面并有效平稳执行迁移工作。下图阐述了雪球的页面迁移标准化流程:
跨端开发方案有很多,在做页面的跨端迁移选型上需要从技术、业务以及可拓展等方面进行综合考虑,才能真正实现降本增效的作用。通过迁移策略的执行,跨端技术在客户端的推广已经初见成效。今年以来雪球客户端新业务和重构页面用 RN/H5 跨端技术占比达到 70%,覆盖私募、公募、社区、股票等多个业务核心链路的数十个页面,总体提升人效 50% 以上。
六、总结展望
6.1 内容回顾
前面几篇文章围绕「跨端容器建设」、「三端同构建设」、「高可用建设」、「跨端迁移建设」几个方面,详细阐述了雪球跨端架构的演进和推广过程,具体包括:
- 明确跨端架构建设的意义和目标
- 介绍雪球跨端架构的总体概览、解决的问题、以及建设路径
- 围绕 RN 容器、Web 容器、JSBridge、原生组件等,阐述雪球跨端容器建设上的主要工作
- 介绍雪球在三端同构建设上的核心能力、以及未来规划
- 从运维监控、容器加载速度优化、容器稳定性优化三个方面,分别阐述雪球跨端高可用建设上的主要工作
- 介绍雪球在跨端迁移建设上的页面选型、迁移策略、流程机制等
6.2 未来展望
通过雪球大前端团队共同参与的跨端架构演进和跨端技术的全面推广,在动态化能力、开发效率、高可用性上均有了明显提升。当然建设一套完善的跨端框架是一项不小的系统工程,这里主要从以下几个方面简要介绍下雪球跨端技术的现状与计划:
能力完善度
能力完善度上,持续积累建设,目前与原生组件相比已经支持了绝大部分视图组件(存在一些 H5 特有的组件)和大部分雪球业务场景下可能使用到的能力 API,但仍不是图灵完备,比如在跨端容器的精细化运营方面依然存在局限性,明年我们会向更细粒度容器方向探索,实现类似于动态列表等更精细化运营能力。
性能质量体验
在性能质量体验方面设立了专人专项跟进优化制度,在容器加载速度等关键性能指标上较优化前有了 60% 以上的明显提升,部分指标项已优于竞品,并且会持续关注和优化;性能监控体系和质量监控体系也正在不断建设完善中。随着 RN 1.0 版本的发布在即,明年我们也会在新架构上进行更多尝试。
周边设施配套建设
在周边设施配套建设上,涵盖 RN 包构建、配置、灰度、发布、运维、运营等全流程的 mPaaS 后台仍在不断完善中,这作为开发者一站式操作平台很大程度提升业务与平台的协同效率,包括更加友好的真机调试等功能的开发者工具也会列入未来计划中。
能力开放
在能力开放方面,目前雪球跨端框架完成雪球、雪球基金、雪球证券三个 APP 的双端接入,新业务和重构页面用 RN/H5 占比达到 70% 以上,提升人效 50% 以上。未来会在更多接入场景上进行探索,例如搭建开放平台,将雪球容器框架的基础能力以标准化 API 的形式统一对外提供,从而面向三方外部开发者,围绕 App 的开放能力构建出丰富多彩的商业生态。
多措并举
从更长远的角度,我们甚至可以将跨端框架和端智能结合,比如我们能够通过智能化平台拖拽生成一个项目模板,然后再通过跨端框架将其编译成对应的平台小程序或者 APP,这就极大的提升了开发效率,并能够降低开发跨端项目所需要的技术人员的能力门槛,也从侧面降低了研发成本,相信这一定也是一个非常有价值的研究方向,未来也一定会在雪球大前端落地。
6.3 小结
随着业务的繁荣和技术的发展,整个跨端技术知识库变得愈发庞大,WVC/BlendUI/CoverView、Taro/uni-app/mpvue、Flutter/Weex/React Native、Qt/Unity/Electron、容器化、端智能、微前端……未来的画卷波澜壮阔,而人的精力有限,因此每个时期找到下一个阶段应该了解和深入研究的领域至关重要。「路的尽头,仍然是路,只要你愿意走」,跨端架构建设不仅仅是今年雪球大前端团队的重要技术目标,相信未来也会通过不断更迭完善助飞雪球业务!
参考资料
【QClient】ReactNative应用自动化性能采集、分析和量化评估方案