性能翻倍!京东亿级体量小程序优化实践 | 京东云技术团队

一、前言

小程序性能是指小程序在微信APP或者其他宿主APP中加载和呈现的速度,以及小程序对用户交互的响应程度。性能欠缺的小程序渲染和响应速度较慢,甚至会出现无法正常打开小程序的情况,在不同程度上极大地影响了用户体验,从而导致用户流失。

京东购物小程序随着更多业务不断的更新迭代,启动性能逐步下降,微信后台打开率仅86%,每天有几百万的流失。随着互联网人口红利的结束,增量变缓,如何通过技术手段提升启动性能成为重中之重,以便更好地留住来之不易的增量用户,进一步助力业务的成长。

二、性能指标与衡量

使用性能指标来评估小程序的加载速度是非常必要的,我们回顾一下京购小程序页面加载的几个关键阶段:

这几个关键阶段的含义如下:

阶段 含义
loadPackage 小程序代码包下载
First Paint (FP) 页面首次绘制
First Contentful Paint (FCP) 首次有内容的绘制
First Meaningful Paint (FMP) 首次有意义的绘制
Largest Contentful Paint (LCP) 页面最大内容绘制,达到可交互状态

在以上的几个阶段当中,我们最为注重的是,用户从点击小程序开始到页面最大内容绘制 (LCP) 的总耗时。

而烛龙监控平台已经为京购小程序开发者们提供了性能监控指标,我们可以通过烛龙监控平台看到对应流程的耗时来作为衡量,小程序启动流程如图:

其中关键的几个监控指标字段和对应意义如下:

字段 意义
_perf_downloadPackage 代码包下载耗时
_perf_evaluateScript 逻辑层 JS 代码注入耗时
_perf_firstRender 页面首次渲染耗时
_perf_route 页面切换耗时
_perf_firstContentfulPaint 首次内容绘制时间(FCP)
_perf_appLaunch 小程序启动耗时
_perf_largestContentfulPaint 最大内容绘制时间(LCP)

烛龙监控平台根据以上几个指标权重和每个指标分值加权平均,计算用户体验值评分(UEI),如图:

三、如何进行性能优化

3.1 代码包体积优化(旨在减少小程序初始化耗时)

从上面的京购小程序启动流程图来看,在app.onLauch执行之前,主要耗时阶段为代码包下载代码注入。而代码包下载和代码注入的时长,均和小程序逻辑代码体积有关。一般而言,小程序代码包越大,逻辑层代码体积也越大,因此初始化耗时也更长。这里我们使用到以下技术:

3.1.1 独立分包

「独立分包」可以独立于主包和其他分包运行。从独立分包页面进入小程序时,不需要下载主包。

开发者可以按需将某些具有一定功能独立性的页面配置到独立分包中。当小程序从普通的分包页面启动时,需要首先下载主包;而独立分包不依赖主包即可运行,可以很大程度上提升分包页面的启动速度。

我们针对于京购小程序首页,利用独立分包开发了京购极速版首页,并逐步投放在发现购物入口。通过和正常包对比,极速版首页的启动耗时总体降低了420ms,优化了14.5%的启动耗时,以上收益来自于Android侧的大盘优化效果。

3.1.2 分包异步化

「分包异步化」将小程序的分包从页面粒度细化到组件甚至文件粒度。这使得本来只能放在主包内页面的部分插件、组件和代码逻辑可以剥离到分包中,并在运行时异步加载,从而进一步降低启动所需的包大小和代码量。

分包异步化能有效解决主包大小过度膨胀的问题。

我们针对于附近生活圈频道页面,通过使用分包异步化将主包组件移至分包,将附近生活圈所占主包空间从126kb降低至39kb,极大地降低了主包代码体积;公共components,经过首页、购物车、我京等模块的共同努力,通过分包异步化从203kb瘦身到31kb;分类tab,通过分包异步化138kb瘦身到6kb,也特别感谢包括"基础业务研发部"等兄弟部门对瘦身工作的帮助和支持。

3.1.3 分包预下载

在使用「分包加载」后,虽然能够显著提升小程序的启动速度,但是当用户在使用小程序过程中跳转到分包内页面时,需要等待分包下载完成后才能进入页面,造成页面切换的延迟,影响小程序的使用体验。分包预下载便是为了解决首次进入分包页面时的延迟问题而设计的。

值得注意的是,独立分包和分包预下载可以配合使用,对于独立分包,也可以预下载主包。

我们针对于附近生活圈频道页面将组件进行分包异步化引入之后,降低了附近生活圈频道各个楼层组件渲染的速度,因此我们通过在首页配置预下载分包组件的方式,通过提前下载附近生活圈分包组件,解决首次进入附近生活圈页面组件渲染延迟的问题。

3.2 代码注入优化

3.2.1 按需注入

我们可以通过小程序配置,有选择性地注入必要的组件代码:

json 复制代码
{
  "lazyCodeLoading": "requiredComponents"
}

「按需注入」启用后,小程序仅注入当前访问页面所需的自定义组件和页面代码。未访问的页面、当前页面未声明的自定义组件不会被加载和初始化,对应代码文件将不被执行,以降低小程序的启动时间和运行时内存。

值得注意的是,插件包和扩展库目前暂不支持按需注入。如果需要实现插件按需加载,可以考虑将插件置于一个分包,并通过「分包异步化」的形式异步引入。

3.2.2 启动过程中减少同步API的调用

在小程序的启动过程中,会进行代码初始化并依次同步执行 App.onLaunch, App.onShow, Page.onLoad, Page.onShow 等生命周期函数,在此期间应尽量减少或不调用同步API。

我们常见的容易在初始化阶段调用的同步API有:

  1. getSystemInfo/getSystemInfoSync,可尝试使用异步版本API getSystemInfoAsync代替;
  2. getStorageSync/setStorageSync,应只用来进行数据的持久化存储,不应用于运行时的数据传递或全局状态管理。

3.3 首屏渲染优化

页面首屏渲染的优化,旨在让用户更早的看到有内容的页面绘制(FCP)或者有意义的页面绘制(FMP)。

3.3.1 避免引用未使用的自定义组件

在页面渲染阶段是,会初始化读取当前页面配置和全局配置中使用 usingComponents 引用的自定义组件,以及组件所依赖的其他自定义组件,引入未使用的自定义组件会影响页面初始化渲染耗时。

因此,当组件不被使用时,应及时从 usingComponents 中移除。

3.3.2 精简首屏数据

页面首次渲染的耗时与页面的复杂程度呈正相关。在京购小程序很多业务场景当中,小程序渲染的页面高度是超过一屏的,但在用户首次进入页面时,超出屏幕的页面内容并没有实际性的意义,因此我们只需要优先渲染出可视范围内的内容即可。当页面首屏渲染完毕后,再继续异步渲染剩下的页面内容。

因此,我们可以对页面首次渲染的内容做一定的取舍,即:

  1. 如果页面从上到下是由多个独立的组件组合而成的,那么我们可以针对不在首屏范围内的组件进行延迟加载
  2. 如果页面是由某个列表项展开构成的,例如首页的feeds流,那么我们可以通过主动控制列表项的长度进行分页加载,在滑动至接近于底部时再进行更多列表项的加载和渲染。

需要注意的是,与页面渲染无关的数据尽量不放在data当中,避免影响页面渲染耗时。

3.3.3 提前首屏数据请求

由于网络请求都需要一定的时间,但我们小程序页面渲染的数据却经常需要依赖服务端的接口返回,在服务端接口返回数据之前我们页面就可能是空白的或者骨架屏。

为了尽可能早发出核心数据请求,我们可以采用微信小程序提供的能力:数据预拉取

「数据预拉取」使得我们可以在小程序启动时,由微信客户端通过微信后台提前向我们服务器拉取核心业务数据,当代码包加载完成时,我们在京购首页通过 wx.getBackgroundFetchData 拿到预拉取的数据,便可以更快地渲染出我们的首页,减少用户等待时间,具体核心流程如图所示:

3.3.4 缓存请求数据用于初始渲染

除去上述的数据预拉取能力,微信小程序提供了 wx.setStoragewx.getStorage 等API来进行本地缓存的读写。

我们在京购首页中,将上一次读取到的直出接口的数据存储在缓存当中,以便用户在下次初始化首页时,优先从缓存中读取首页直出数据用来快速渲染页面整体视图,待接口真实返回后再进行页面更新。

3.3.5 骨架屏

「骨架屏」用于页面渲染之前,通过一些灰色的区块大致勾勒出页面的轮廓占位,待页面数据加载完成后,再替换成真实的内容。骨架屏能够有效地避免页面在请求过程展示白屏,避免用户误以为加载失败而退出小程序;且能够避免不同楼层异步渲染的过程中上下跳动,影响用户体验。

3.4 发版更新频率等其他优化

3.4.1 合理规划版本发布

通过小程序更新机制可以得知,小程序在启动时如果检测到版本更新,会重新获取小程序的基础信息、重新生成初始渲染缓存等操作,从而影响页面启动耗时。过于频繁的发版更新频率可能会导致用户每次重新打开小程序都需要进行小程序的更新,使得平均启动耗时变长。

因此我们应提前做好版本规划,控制版本发布更新的频率。

3.4.2 优先使用本地版本设置

通过「优先使用本地版本设置」,判断某些较新的小程序版本无需强制用户更新到最新版本。可以在小程序管理后台「设置」-「功能设置」-「优先使用本地版本设置」进行设置,设置后,当用户使用小程序时,客户端会优先打开本地版本代码包。若用户本地版本不低于该版本,则优先使用本地版本,后台异步更新最新版本的代码包。若低于该版本,则优先更新到最新版本。

四、总结和展望

4.1 性能优化总结

看到这里,在了解了京购小程序的性能指标和启动流程后,我们已经能够很容易地从烛龙监控平台中,查看我们所负责业务对应的页面性能数据,及时发现我们页面的一些性能问题并及时进行优化。

当然,除去以上阐述的几点前端性能优化策略之外,我们也可以通过考虑与服务端约定新的数据格式,避免服务端传输无效数据导致响应内容体积过大等思路进行探索。

在经过我们上述多种优化后,从微信官方后台we分析中的数据可以看出,京东购物小程序的打开率从原先的86%提升到90%以上 ,相比优化之前每天减少近百万用户流失

下图是内测阶段京东购物小程序优化前后的真机体验对比,从烛龙监控平台上看,首页整体冷启动耗时从4800ms左右降至2500ms以下。目前线上灰度过程中,用户随机50%命中优化后版本,后续会根据业务数据评估,逐步提高灰度比例,全面提高用户体验。

4.2 未来展望

基于我们当前在性能优化路上的探索和实践,结合实际线上的统计数据分析,在后续我们也仍会针对于「页面首次渲染」等耗时占比较大的流程进行深入的实践,在「渲染性能优化」层面做更多的尝试,从精简业务数据层面、尝试新的渲染引擎Skyline等等,为客户提供更加友好流畅的使用体验。

作者:京东零售 黄宏峰

来源:京东云开发者社区 转载请注明来源

相关推荐
热爱编程的小曾22 分钟前
sqli-labs靶场 less 8
前端·数据库·less
gongzemin34 分钟前
React 和 Vue3 在事件传递的区别
前端·vue.js·react.js
Apifox1 小时前
如何在 Apifox 中通过 Runner 运行包含云端数据库连接配置的测试场景
前端·后端·ci/cd
树上有只程序猿1 小时前
后端思维之高并发处理方案
前端
庸俗今天不摸鱼2 小时前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
黄毛火烧雪下2 小时前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox2 小时前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞2 小时前
Firefox默认在新标签页打开收藏栏链接
前端·firefox
高达可以过山车不行2 小时前
Firefox账号同步书签不一致(火狐浏览器书签同步不一致)
前端·firefox
m0_593758102 小时前
firefox 136.0.4版本离线安装MarkDown插件
前端·firefox