导读|H5开屏龟速常是令开发者头疼的问题。腾讯企业微信团队对该景象进行剖析优化,最终H5开屏耗时130ms,达到秒开成果!企微前端开发工程师陈智仁将分享可用可扩大的Hybird H5秒开计划。该团队应用离线包解决了资源申请耗时的问题,在这个根底上通过耗时剖析找到瓶颈环节,进一步采纳“预热”进行优化提速以解决了WebView初始化、数据预拉取、js执行(app初始化)耗时的问题。心愿这些通用办法对你有帮忙。

背景

服务端渲染(SSR)是Web支流的性能优化伎俩。SSR直出相比传统的SPA利用加载渲染躲避了首屏拉取数据和资源申请的网络往返耗时。团队针对Web开发也曾经反对了SSR能力。近期出于动态化经营的思考,咱们抉择了Web开发,同时咱们也接到了晋升体验的诉求。

以企业微信要开发的页面为例:采纳SSR计划,从用户点击到首屏渲染的耗时均值约600ms,白屏工夫的存在是能够感知到的。为了尽可能打消白屏达到秒开成果,咱们尝试做更多摸索。

计划思路

1) 计划选型

如何实现页面秒开呢?从最直观的渲染链路来动手剖析。下图列出了从用户点击到看到首屏渲染可交互,一个SPA利用次要环节的加载流程。咱们调研了业内相干计划,从渲染链路的视角来看下常见计划的优化思路。

  • 传统离线包

在加载渲染过程中,网络IO是很显著的一个耗时瓶颈。传统的离线包计划思路很间接,如果网络耗时那就将资源离线,很好地解决了资源申请的耗时。用Service Worker也能达到离线包的成果,同时也是Web规范。首次渲染优化个别须要联合客户端配置预启动脚本来达到缓存资源的成果。

  • SSR

SSR则从另外的角度登程,在申请页面的时候就进行服务端数据拉取和页面直出,首屏得以在一个网络往返就能够展现,无效地躲避了后续须要期待css/js资源加载、数据拉取的工夫。性能体验有比拟大的晋升,在BFF遍及的状况下开发模式简略,很受欢迎。

  • 公司内相干工作

思考到WebView的初始化(冷启动/ 二次启动)、页面网络申请、首屏数据接口的耗时,白屏工夫还是可感知地存在的。以咱们要开发的页面为例采纳SSR首屏耗时均值~600ms,可交互工夫均值~1100ms。如何进一步打消白屏?这里为各位介绍公司内外针对h5首屏性能优化的优良计划。

手Q团队的VasSonic是集大成者,次要思路是采纳WebView和数据预拉取并行的形式。这套计划须要客户端和服务端采纳指定协定革新接入,开发时也有肯定的革新工作。

微信游戏团队次要思路是利用jsCore做客户端预渲染,用户点击后间接上屏。这个办法也达到了很好的成果,首屏FCP工夫从1664ms升高到了411ms。

咱们做了一个简要的计划比照,能够看到每个计划都针对渲染链路的某个或多个环节做了优化,其中VasSonic的成果比较显著。不过联合企业微信业务理论状况,咱们列出了如下几点思考:

首先,接入对客户端和服务端有肯定的革新老本,业务开发也有肯定的革新工作。其次,咱们曾经有一套的对立公布平台,心愿能复用这套公布能力。最初,性能上有没有进一步优化的空间呢?业务需要对体验上的要求是心愿达到更好的性能成果或者说尽可能齐全地打消白屏

基于以上思考,咱们在上述计划的根底上做了进一步的实际摸索,以冀望达到更好的性能成果。

2)计划架构

为了达到尽可能齐全打消白屏,咱们还是从初始问题登程,联合渲染链路进行剖析,思路上针对每个环节采取对应的优化办法。

每个环节的优化在具体落地时会存在着计划的利弊取舍。比方预拉取数据个别的思路是交给客户端来做,然而存在着客户端申请和h5申请两套机制(鉴权、申请通道等方面)如何协调的问题。在渲染链路剖析时,如果业务的js执行也奉献了不少耗时,有没有可能从通用根底计划的角度来解决这个问题,同时也能缩小业务对性能优化的关注?这是个值得各位思考摸索的问题。具体的内容会在前面开展来说。

如图展现了计划的优化思路和主流程。计划应用离线包解决了资源申请耗时的问题,在这个根底上通过耗时剖析找到瓶颈环节,进一步采纳预热的思路进行优化提速,解决了WebView初始化、数据预拉取、js执行(app初始化)耗时的问题,最终达到了现实的性能体验。

图1 上屏流程

图2 计划架构

上面咱们具体介绍下计划,包含:离线包技术、预热提速和进一步的优化工作。

离线包减速

为了躲避资源申请耗时,咱们应用了离线包技术。离线包技术是比拟成熟的计划,相干打包、公布拉取的计划这里不多说了,次要说下计划中一些设计上的考量。

1)加载流程

咱们通过offid作为离线包利用的标识,fallback机制保障离线资源不可达时用户也能够失常拜访页面,通过离线包预拉取和异步检测更新机制进步了离线包命中率,尽可能打消了网络资源加载的耗时。

2)fallback机制

因为用户网络情况的不确定性,离线包加载可能存在失败的状况。为了保障可用性,咱们确定了离线包加载不阻塞渲染的思路。当用户点击入口url,对应offid离线包在本地不存在时,会fallback申请现网页面,同时异步加载离线包。所以咱们针对离线包的打包构造,依照现网URL path来组织资源门路。这样客户端申请拦挡解决也会比拟不便,不须要了解映射规定。当发现离线包不匹配资源时,放过申请透到现网即可。如图展现了咱们的离线包构造示例。

3. 离线包生命周期

为了进步离线包命中率,咱们会配置一些机会(e.g.入口曝光)来预拉取离线包。

离线包的更新机制:客户端加载时依据offid检测到本地离线包的存在,则间接应用拉起,同时启动异步版本检测和更新。如果新包版本号大于本地版本号则更新缓存,同时公布平台也反对辨别测试环境、正式环境以及按条件灰度。

上了离线包后,能够看到页面的首屏耗时均值从基准无优化的1340ms降到了963ms,离线包的预拉取和更新策略则使离线包命中率达到了95%。首屏耗时失去了肯定的升高,但也还有比拟大的优化空间,须要更一步的剖析优化。

预热提速

通过离线包的减速,咱们解决了资源申请耗时的问题,不过从整个渲染链路来看还有很大的优化空间,咱们做了具体的耗时剖析,找出耗时瓶颈,针对耗时环节做了进一步的优化提速

1)耗时剖析

离线包技术躲避了资源申请耗时,然而从整个渲染链路来看还有很大的优化空间,咱们做了耗时剖析如下。

Hybird利用中,WebView初始化是比拟耗时的环节,这里咱们针对iOS WebView做了测试。

数据拉取方面,不同入口页面的耗时不一,某些入口页面比拟重的接口耗时超过了1s。

此外,咱们发现js执行也奉献了不少耗时。以某入口页面为例,框架初始化工夫~10ms,app初始化工夫~440ms。

2)渲染链路预热提速

  • 预热流程

咱们的指标是打消白屏,这里现实的计划是找到一种和业务无关的通用解法。计划的次要思路是预热,把能提前做的都做了。预热是不是就是把WebView提前创立进去就好了呢?不是的,这里的预热波及到多个渲染环节的优化组合。如图展现了预热的整体流程,上面一个个来解。

2)WebView预创立

为了打消WebView的耗时,咱们采取了全局的预创立WebView,机会为配置入口曝光。不过全局复用预热WebView不可避免地会引入可能的业务内存泄露问题,下文会介绍对应的躲避计划。

  • 数据预拉取

数据拉取是页面渲染的一个耗时环节。为了打消数据预拉取耗时,在预创立WebView阶段咱们同时进行了数据预拉取。

数据预拉取常见的思路是交给客户端来做,然而存在着客户端申请和h5申请两套机制如何协调的问题,以申请鉴权为例,存在以下的问题:

第一,Web团队本身有一层node BFF,实现了相应的数据拉取业务逻辑,而客户端则走的公有协定通道申请C++后盾,二者是不同的鉴权机制。

第二,如果交给客户端来做,能够接入HTTP申请这套机制,革新老本比拟大,如果复用原有通道,则一份数据业务逻辑须要两套实现。

如何设计一套通用可扩大的计划?咱们心愿做到客户端只关注容器的能力(预热、资源拦挡等),屏蔽掉更深刻的对Web的感知,这样的解耦能够无效管制计划的复杂度。因而,这里咱们针对离线包配置项减少了preUrl字段,使客户端保护更通用的能力,数据预拉取交给业务团队来做,具体如下:

第一,客户端:拉取某个离线包配置项时会读取该字段,同时针对以后曝光的入口url可能存在多个有着不同的数据需要,这里会进行收集,将曝光url中的业务key参数拼接到preUrl来初始化WebView,这些作为通用能力。

第二,业务:preUrl页面在加载时会拉取相应的业务数据存到localStorage,理论的数据预拉取申请放到业务方发动,也能够很好地兼容已有的技术栈。

  • JS预执行

很靠近指标了,最初js执行的耗时能不能打消呢?首先来看下440ms的耗时具体在哪里,通过剖析看到,框架初始化仅须要不到10ms的工夫,而真正的大头在业务代码的执行,其中代码编译耗时~80ms,其余的都是业务app初始化执行工夫,这个是业务自身复杂度造成的。

咱们首先思考了创立两个WebView的计划,一个负责加载preUrl预拉取数据,另一个负责loadUrl上屏,这样设计上比拟简洁强壮,不过实际下来发现成果不现实,如图展现了该计划的成果,渲染不稳固能够感知到白屏的存在。在曾经有了预拉取数据和离线资源的状况下,实践上用户点击后须要期待的就只有渲染这块的耗时,理论咱们发现在简单利用初始化时存在js执行耗时较大的问题。

最终咱们做了一个预执行的解法。联合SPA的特点,将preUrl作为SPA的一个子页面,不须要UI展现,只负责预拉取数据,这样子页面加载实现的同时也实现了app提前初始化。而相应的不同入口切换页面时,不同于复用预热WebView从新reload页面,为了保留app初始化的成果,咱们采取了一套Native告诉Web SDK,页面切换交给WebView管制的计划。其中,Native告诉则以调用SDK全局办法的形式。通过这种形式,入口页面间切换其实只是hashchange触发的子页面渲染,达到了不错的成果。流程图即预热计划的上屏局部。

该计划执行后咱们达到了预期指标成果,最大限度地打消了白屏靠近Native体验。需要上线后通过监控数据能够看到在命中预热和离线包逻辑的状况下,从用户点击到页面上屏可交互耗时均值约130ms。

进一步优化

1)离线包平安

在离线包平安方面,为了避免包篡改,每咱们次打包公布时都会生成包签名和文件md5。客户端在应用解析离线包时会校验完整性,在返回离线资源时会校验文件完整性。

2)稳定性

整体计划在性能上曾经达到目标了,保障稳定性对产品体验也很重要咱们为了打消js执行的耗时,采取了Native告诉Web SDK管制页面切换的形式。尽管比拟灵便然而也带来了稳定性的问题。具体来说,如果SDK在做页面切换时异样,之后用户关上每个入口url都会看到雷同的页面。入口页面的业务在用户应用过程中如果跳转了非SPA的链接同时没有注入SDK,之后的页面切换也会生效。

如何保障预热容器的可用性呢?咱们设计了一套告诉机制确保客户端感知到预热容器的可用状态,并在不可用时得以复原,如图。预热容器会保护isInit和isInvokedSuc两个状态。只有当preUrl胜利加载和SDK执行胜利上屏时,两个状态才会置true,此时的预热WebView才是可用的,否则会回退到一般容器模式进行load url来加载页面。

此外,在每次入口url曝光时,已有的预热容器也会销毁重建,也无效保障了容器的稳定性。

3)内存泄露

应用全局的预创立WebView,不可避免的会引入可能的业务内存泄露问题。在测试过程中,咱们也发现了这种例子。能够看到当点开应用了预热容器的页面后搁置一段时间,整个内存在一直上涨,最终会导致PC端页面的白屏或者挪动端的Crash,这个情况最终归因是业务逻辑的实现存在缺点。

不过在根底技术的角度而言,开发者也须要采取措施来尽可能躲避内存泄露的状况。次要思路是缩小同一个预热容器的常驻,也就是对存活的容器设置有效期,在适当的机会查看并清理过期容器,咱们抉择的机会是App前后台切换时

4)解决副作用

出于性能思考,咱们抉择了通过Web SDK管制页面的计划,同时应用了全局的预创立WebView。这带来了副作用——当页面对容器做了全局的设置,可能会影响到下一个页面的体现。比方:设置document.title、通过公有JSAPI设置了WebView导航栏的表......

当执行这些操作时,在下一个页面也复用预热容器的状况下,全局设置没有失去清理重置或者笼罩,用户会看到上个页面的体现。

为了解决上述问题,业务能够在每个页面被动申明须要的体现来笼罩上个页面的设置,现实的办法还是根底技术来躲避这个问题来保障业务开发的一致性。咱们在SDK管制切换页面时,进行了一系列的重置操作。

此外,在Windows和Mac端,咱们也设计了双预热WebView的计划来齐全解决这个问题。每次应用时同时创立新容器,得以保障每次关上入口页面都是应用新创建的容器。当然,计划的另一面则是会带来App内存的上涨。

总结

咱们从渲染链路动手,针对每个环节进行剖析优化,最终积淀了一套可用可扩大的Hybird H5秒开计划。从渲染链路的角度来看,计划通过离线包和预热一系列优化,将用户从点击到可交互的工夫缩短到了一个SPA路由切换上屏步骤的耗时。

上线后咱们监控发现,命中了预热离线逻辑的页面首屏耗时~130ms,相比于离线包、SSR都有劣势,同时预热离线容器命中率也达到了97%,达到了现实的体验成果。心愿本篇对你有帮忙。

腾讯工程师技术干货中转:

1、算法工程师深度解构ChatGPT技术

2、10分钟!从架构视角读懂K8s

3、探秘微信业务优化:DDD从入门到实际

4、耗时减半?腾讯云OCR只做了3件事