H5 在转转 App 中有着较高的比重,承载了 B2C,C2B,C2C 多个业务的巨大流量,转转在前端技术这块,考虑到业务架构和人员结构的情况,没有采用 ReactNative,Weex,小程序等技术,一直在 Hybrid 领域进行深耕。
从 App 诞生之初直到现在,转转的 Hybrid 体系经历了一些迭代和演进。之前公众号历史文章曾经发布过《转转Hybrid体系建设》,这篇文章是站在前端视角为我们展现了其中一部分内容。今天我尝试客户端的视角来回顾下转转 Hybrid 体系的建设历程,在这个建设历程中,有着一些我们的困惑、挫败、思考以及喜悦,希望与屏幕前的你,一起分享。
转转的 Hybrid 体系建设,经历了几个重要的时期,我把总结为下面三个阶段:
下面,我来详细阐述下这三个阶段。文章比较长,信息密度不大,一起来吧 :)
在转转 App 发展的前几年,大概是 2015 年 ~ 2020 年上半年这段时间,Hybrid 缺少系统的规划,我们刚出茅庐,Hybrid 在我们眼中是什么呢?
一个WebView,加载一个 H5,将前端内容加载展示出来,H5 JavaScript 与 App 进行通信交互,仅此而已。
我们当时还不能很好的意识到,一个成熟的或者理想中 Hybrid 体系应该要建设成什么样。
这时候 App 承载的业务形态在不断的尝试中,前端与 App 的通信接口,也在野蛮不断扩充添加着。A 业务今天来了个需求,说我们需要加一个通信接口,OK,前端和 App 同学约定好,测试了下没发现问题上线🚀。B 业务明天来了一个需求,说我们也需要一个,OK,再来,接口不断添加着。
从开发过程,到测试过程,最后到上线运行,问题也随之慢慢的暴露出来。
我们没有接口相关的文档或者非常的缺乏,前端同学在开发使用的时候,无法很好的知晓 App 目前有哪些能力可以使用,以及接口的参数和返回值是什么样的,更多的是靠查看代码和询问客户端同学,非常的不方便,沟通成本还是挺高的。
另外就是由于没有合理的规划和接口分类,造成了不少接口的功能是类似的,但实现上有一些差异,会给前端同学在使用上造成困惑,究竟用哪一个才对。
在通信接口开发时,客户端同学自测怎么做的呢?Android 和 iOS 客户端内置了一个 html 文件,里面有一些通信的接口,每次都往这个文件添加,然后编译到手机上进行测试。
非常的不方便,每次要添加接口都要修改本地内置的 html 测试文件,然后进行编译,但并不是所有人都可以正确的编写这个 js 测试接口。
线上页面出现问题,客户端同学是比较抓瞎的:
一方面是我们的日志上报还不够全面,另一个是没有方便的小工具可以直接查看,只能通过本地调试 app 查看断点数据和本地 log,排查定位问题的链路比较长,有些低效。
另外一个显著的问题就是线上我们的 WebView 出现了安全问题,有骗子利用转转没有限制页面打开或者限制页面存在漏洞的情况,引导用户在 App 内打开骗子网页完成支付,用户被骗投诉到平台,给我们的口碑也造成了不小的影响。
上面的问题已经暴露,加之本身 WebView 也开始面临着不少问题,重新设计一个全新 Hybrid 架构体系迫在眉睫。建设这样新体系囊括了客户端 WebView 的重构,以及相关平台的建设,再到 JS SDK的优化,涉及到了整个大前端基建层面。
我们在 2020 年中的时候,基本已经完成了新的 Hybrid 体系设计,截止到目前,整体是这样的:
历史 WebView 面临着诸多问题,比如
在 Android 和 iOS 两端,我们重写了 WebView,并积极的采用了 Kotlin 和 Swift 语言。重写之后,将上面面临的问题就全部解决掉了。上线之后,经过一段时间的灰度,对存在的问题进行不断优化和调整,最终进行了全量,且将项目的 legacy webview 代码全部删除下线。
至此,我们在混乱治理中,迈出了最重要的第一步。
2020 年 5 月 6 号,转转和找靓机合并,找靓机的整个 WebView 体系和转转还不太一样。找靓机使用的基于开源方案 DSBridge 的通信方式。
DSBridge这套开源方案其实挺优秀:
优点有很多,但这些优点并不是我们考虑的重点,合并融合后,我们需要考虑的重点是:
核心就是如何统一 WebView,更多的是考虑如何让找靓机的 WebView 平滑过渡到转转的 WebView 体系中来。
转转使用自定义的通讯方案,有 200多 个 API,找靓机采用了 DSBridge 通讯方案有 100 多个 API。要考虑到 H5 在多 App 的运行该怎么做呢?
在 App 早期,使用 iOS 7 系统的用户还有一定的占比,为了兼容这些用户,那时候我们并没有使用 WKWebView。
UIWebView 我们采用的是基于 iFrame url 拦截解析获取到执行函数名和参数的方式来实现 JS 调用 Native。后面随着 iOS 7 用户的缩小,以及 iOS 7 带来的额外维护性问题,我们逐步放弃了 iOS 7,应用最低的支持版本从 iOS 7 变成 iOS 8(现在最低iOS 11),此时历史 UIWebView 的通信接口 API 作为存量,很多业务在用,也一直保留到了现在。
取而代之的是采用 WKWebView。毕竟 WKWebView 相比 UIWebView 有着非常大的优势,独立进程和更好的 JS 引擎带来的性能提升以及 WKWebView 在稳定性、内存管理、兼容性、安全性、后台加载等方面的表现都更优于 UIWebView。
所以,在转转 App 中,由于 iOS 7 的历史原因,存在着两套通信方式:一个是兼容 UIWebView 的 iFrame url 拦截解析通信的方式,另外一个就是 WKWebView 的 WKScriptMessageHandler 协议通信方式。
融合初期,为了支持找靓机的 H5 通信,转转 iOS 这边移植新增了 DSBridge 的实现,使得找靓机的 H5 完美运行在转转 WebView 容器之上,此时,接口调用的 Native 能力仍然是找靓机侧的。
后面随着融合的不断加深,基础能力也在不断的扩大,仅有一些少数存量的接口仍然会调用到找靓机的 DSBridge 老接口 API,大部分都会调用到客户端提供的基础能力 API 以及找靓机新增的转转式规范接口 API。
在整个 Hybrid 体系建设中,平台的建设是不可忽视的。我们不断收集一线的反馈,和前端支撑组不断磨合,由前端支撑组打造出统跳、接口管理、离线包、预渲染等平台。这几个平台和客户端关系紧密,还有一些其他平台如前端的性能平台、异常监控平台、报警平台等等,和客户端这边关系不大,这里就不展开说了。
统跳平台:在 Hybrid 中进行路由跳转的信息,由统跳平台统一管理维护
接口管理平台:解决 Native 提供的接口能力的增删改查问题,修改后,一键同步更新到前端文档页面。
无规矩不成方圆,没有规范,就容易引发破窗效应与混乱,长期下去,积重难返。
WebView Cookie、UA、Url Query、通信格式规范,详情见:转转Hybrid-SDK重构和实践 一文中的『转转解决方案』部分。
骗子通过聊天向用户发送一些骗子网页,用户中招被骗钱后投诉到平台,这样的情况我们遇到了不止一个。后面我们经过白名单的多次升级,逐步完善了整个白名单体系。
从添加域名的审批,到域名的定时清理,以及精细到 url path 级别的授信级别,使得近两年来,骗子通过钻白名单漏洞去行骗的的情况几乎被完全封杀掉。
我们经常收到前端同学的吐槽:
有一段时间,我们会被拉入各种各样的群里,面对群里抛出的类似上面的问题,解决起来非常的心累。
不行了,得专门搞一下了。遂立项大禹,治水行动开始。
治水基本方针:
经过大禹多期的优化,我们将页面栈管理、分享、相册选图、日历、扫码、通讯录、通用存储、剪贴板、系统权限等多个基础能力,进行了统一。没有数据统计,这个带来了多大的收益,但是明显的感觉,被拉群的次数少了,之前的一些长期被吐槽的接口也很少再收到吐槽了。
但大禹治水不能停下来,这是一件需要长期坚持做下去的事情。
性能优化其实不是最后才开始做的,而是从头到现在,一直在进行,只是阶段性的重点不太一样。
早期我们在大前端领域优化手段做的比较有限,包括了
除了上面几点,前端同学也在积极的尝试各种优化方案,包括但不限于:
2021 年是 Flutter 高速发展的一年,但是官方的 Flutter for Web 并不如人意,此时阿里的 Kraken 北海(目前的WebF)方案的出现,让我们看到了另外的一种可能。
Kraken 方案上层采用 Web 技术栈进行开发,无论是 Vue 还是 React 又或者其他如 Angular,通通不限制,底层使用 Flutter 引擎自渲染,保证多端的一致性,使用 AOT 编译将 Kraken 编译成机器码,提供接近原生的性能。由于上层基于 W3C 标准实现,所以拥有非常庞大的前端开发者生态。
我们联合前端同学,在 Kraken 方向做了一些尝试:将转转内的“附近的人”页面,试着用 Kraken 实现。
通过这个页面的改造,我们看到了 Kraken 还存在了许多不足,还不足以支撑我们在线上大规模使用。
存在的问题包括但不限于下面列举的一些:
由于上面存在的问题带来的高额的开发调试以及维护成本,我们暂停了在 Kraken 方向的投入。但对后续也就是目前的 WebF 仍然保持着关注。
除去 Kraken 等方案的尝试,我们积极的在探索和寻找其他优化方式,最终确定了离线包、预渲染、预加载、预请求、接口请求代理、图片缓存共享等多个优化手段。
离线包消除静态资源下载耗时、预渲染消除⻚⾯初始化耗时、预加载提前缓存常用静态资源、预请求提前获取主屏数据解决⻚⾯跳动和⽹络请求链路⻓、图片缓存共享将 Native 图片缓存共享给 H5 提供视觉占位等,通过一系列多级优化方案,前端可以灵活的组合上面的优化手段,使得性能体验大大提升。
使用预渲染的页面,就是秒开的体验,平均首屏时间从 1500ms 下降到 400ms 左右。关于预渲染这块,后面有机会的话,我们会单独再开一篇文章一起深入聊聊。
上面提到的这些方案具有正交性,可以随意进行组合接入。
正常无优化的 H5 加载展示流程:
离线包:
预渲染+预请求:
无预渲染+预请求:
预渲染+预请求+图片缓存共享:
下图点开可以动
我们很关注前端开发同学的开发、调试体验,以及在线上排查问题的时候,客户端能做什么可以更快的帮助定位问题。
基于这个简单的愿景,我们开发了 WebView 小工具。
概览:在概览中,我们可以清晰看到 API 接口日志,离线包,预渲染等优化手段的命中情况,此外我们也可以直接控制 eruda 调试工具以及 UI 走查小工具的开关,以及页面的 Url,Cookie,Query,UserAgent 等情况。
API接口日志: 详细记录了 JS call Native 的每一个接口,包括 request 和 response 详情。
预渲染: 记录了预渲染当前的下发配置,以及预渲染模版的当前状态,可以方便查看模版是不是已就绪还是渲染中或者被加入了黑名单。同时也添加了客户端的预渲染日志,方便排查页面为什么没有命中预渲染。
。
预请求:记录发出了哪些预请求,以及这些预请求的 request 和 response 详情。
离线包:开启和关闭离线包功能,删除离线包缓存,以及展示离线资源的命中记录。
接口代理:展示接口代理记录以及接口的 request 和 response 详情。
图片缓存共享:记录缓存共享请求,包含目标图片和命中图片地址。
持续优化前端的接口文档以及平台,在接口的用途、参数和响应描述方面力争清晰和准确,且对接口的升级改动形成变更历史等,方便追溯和兼容。
统一大前端的埋点,建立更全面的埋点和监控,如页面白屏时长、大前端全链路埋点监控等,实现对 H5 页面的全方位把控。
还有很多事情需要做,如
HarmonyOS Next 的即将出现,意味着要基于纯鸿蒙系统重新构建一套 WebView 解决方案,整个架构体系和当前可能是一致的,但可能会融合鸿蒙系统的原子化服务以及多设备流转等特性,提升用户在 HarmonyOS 上的 Web 使用体验。