🚨【关注】专题,快人一步了解本专题最新内容 👉一键关注👈
本文收录于【技术新风向】专题,了解专题中更多精彩内容,欢迎点击了解>>
为什么要进行性能优化?
- 在使用小程序的过程中,用户可能会遇到小程序打开慢、滑动卡顿、响应慢、白屏等问题,极其糟糕的情况下小程序可能完全无法使用,这些问题会严重影响用户体验,进而严重影响小程序的用户留存率和转化率;而这些问题都与小程序的性能相关,由此可见,性能问题归根到底就是用户体验的问题,如果能解决好这些问题,用户将更愿意继续使用它。
- 对小程序进行性能优化可以减少小程序的资源占用,从而提高小程序的稳定性和可靠性,减少小程序运行过程中崩溃的可能性,从而提升用户的满意度。
- 平台会对小程序的性能进行评分,性能分高的小程序会得到平台更多的流量扶持,推荐给更多用户使用。
参考:https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/guide/experience-optimization/meaning
开发过程中需要关注哪些问题?
1、场景问题
1.1 高并发问题
抖音小程序相比于其他平台存在一些特殊小程序入口,如抖音直播间的讲解卡,小雪花等(详细入口示例见体验一致性问题);这些场景下的入口使得小程序的瞬时流量会比其他平台大很多;大流量意味着高并发,因此开发者在设计小程序时,需要提前考虑这种场景;例如,饮品售卖的商家在抖音直播间的小雪花入口挂载了抢购饮品的小程序,当商家开启抢购活动时,会出现大量用户同一时间段,访问同一接口抢购同一商品,这便有造成该接口产生高并发问题。
1.2 体验一致性问题
抖音小程序的入口数量和种类众多,在业界领先水平,常见入口如下表所示:
入口名 | 直播间小雪花 | 直播间讲解卡 | 短触区组件 | 视频锚点 |
入口示例 |
入口名 | 视频评论区 | 视频底部链接 | 搜索页外露锚点 | poi详情页 | 固定入口 |
入口示例 |
多入口给小程序的开发者带来大流量的同时也可能带来用户的体验一致性问题;例如,开发者在直播间讲解卡、视频锚点挂载小程序的商品详情页,而在直播间小雪花、挂载的是小程序首页,那么从不同入口进入小程序的用户就会进到不同的页面,会造成用户的体验一致性问题,这是开发者和商家在进行促销活动前需要提前考虑到,并进行正确的入口配置。
2、通用启动问题
2.1 加载速度慢
小程序的加载速度是影响启动性能的关键因素,影响小程序加载速度的主要因素是小程序代码包体积,代码包的大小直接影响下载耗时。
2.2 首屏渲染慢
小程序的首屏渲染慢的具体体现是,小程序在启动后到第一次显示出完整界面所需的时间长。如果首屏渲染时间过长,用户可能会感到不耐烦,从而影响用户体验。造成首屏渲染慢的常见原因有以下几方面:
(1)同步逻辑阻塞渲染
- 在首屏渲染的主线层使用了较多的同步逻辑,例如,在App.onLaunch,Page.onLoad,这些生命周期中有大量的同步代码,并且调用了较多的同步 API 等。
(2)网络请求频繁且耗时长
- 为了保证小程序的实时性,首屏所需很多数据需要进行网络请求,如果网络请求次数过多,且单个网络请求的耗时较长,请求发送的时机相对较晚会,都会造成首屏渲染的明显延迟。
(3)图片资源问题
- 首屏使用了较多大尺寸,大体积的图片资源,未使用 CDN ,也开启图片缓存导致每次首屏渲染时,图片加载和渲染时间长拖慢首屏渲染速度。
(4)页面结构复杂
- 首屏页面开发实现时,使用了过多的不必要的 TTML 元素和 CSS 样式,并且未使用按需渲染对首屏可视区域外的页面进行懒加载。
3、通用运行时问题
3.1 频繁调用 setData
小程序的逻辑层和视图层是两个独立的运行环境,不能直接进行数据共享,需要进行数据的序列化、跨线程/进程的数据传输、数据的反序列化,因此数据传输过程是异步的、非实时的。数据传输的耗时与数据量的大小正相关,如果对线程处于繁忙状态,数据会在消息队列中等待,导致处理队列阻塞,界面渲染不及时而导致卡顿,应避免无用的频繁调用。
3.2 TTML 节点数量多和层级嵌套深
TTML 的节点树太大会增加内存的使用,同时样式重排时间也会更长,大量且深层次的节点嵌套会造成明显的页面卡顿,影响用户体验。
3.3 未合理使用自定义组件
(1)未合理拆分自定义组件
- Page 中的 setData 会触发渲染层以页面级别进行 diff 操作,如果页面比较复杂且没有使用自定义组件,那么 diff 的成本会很高,导致体验比较差(更新卡顿、不粘手等感受)。
- 自定义组件的使用会引入前置的注册和自定义组件生命周期执行逻辑,盲目拆分会增加这部分逻辑的执行耗时。
(2)随意注册当前使用的组件
- 小程序框架为了保证自定义数据在不同的页面实例中也是不同的实例,会在页面创建时对这部分数据(函数类型字段除外)做一次深拷贝,如果开发者在usingComponents中随意注册未使用的自定义组件,会造成额外不必要的开销。
(3)随意使用同步修改 data 的能力
- 在app.json 中增加配置component2: true 后,支持在 created 生命周期中修改自定义组件初始数据,自定义组件将在created 生命周期执行完成后开始渲染。
3.4 内存占用高
(1)事件绑定未及时解绑
- 事件绑定未及时解绑可能会导致不必要的事件触发影响用户体验的同时可能会导致内存泄漏,从而导致内存占用量过高,内存占用量过高导致小程序卡顿、闪退或崩溃。
(2)定时器未及时清理
- 未及时清理定时器可能会导致内存泄漏,从而导致内存占用量过高,内存占用量过高导致小程序卡顿、闪退或崩溃。
3.5 随意创建绑定事件
(1)未合理绑定和处理 scroll 事件
- scroll 事件一般会被高频触发,如果开发者未对相关事件进行有效控频,并在相关事件中高频执行耗时操作,会明显降低 FPS。
(2)未去掉不必要的事件绑定
- 过多的事件绑定(ttml 中的 bind 和 catch),也会增大通信的数据量和次数。
3.6 未正确适配分屏
抖音小程序相比于其他平台会在,直播小程序卡片、POI 商品详情页等入口会出现七分屏场景(小程序的页面未撑满整个视口,只占视口的70%),这种场景下需要进行相关尺寸的视口适配,避免引起布局错乱影响用户体验。
怎么量化问题 (怎么判断性能好坏?用哪些指标?)
1、怎么判断性能好坏?
目前抖音小程序给开发者提供了关于小程序性能评测页面,开发者登陆抖音小程序后台后,可以在“开发”->“性能分析”->“性能评测”中查看评测结果作为参考(对于 pv 低于 10000 的小程序相关数据可能不够准确,仅供参考);同时可兼顾使用本地性能测试工具:客户端性能分析工具、性能 Trace 工具、体验评分工具对小程序性能进行本地分析。
2、指标体系
为了准确衡量小程序的性能及稳定性,我们建立了一系列指标来衡量小程序在启动过程中页面加载的关键阶段的性能以及小程序运行时的稳定性。
性能体验指标:
指标名 | 代码执行耗时 | First Paint(FP) | First Meaningful Paint(FMP) | JsError 异常率 | 启动各阶段到达率 |
含义 | 执行小程序的App.js及App.js中引用文件的耗时; | 小程序首页首次渲染完成耗时(ms) | 小程序首页最大元素渲染完成耗时(ms)。 | JSError 异常占比,JSError 次数/小程序启动次数。 | 小程序首屏加载过程中各阶段启动到达率,包含 FP 到达率和 LCP 到达率。 |
稳定性指标:
指标名 | 页面白屏率 | 异常弹窗数 | 网络请求失败数 | JS API 失败率 | 帧率 |
含义 | 小程序退出时上报,页面白屏次数/页面白屏检测次数。 | 页面异常弹窗出现的数量。 | 网络请求失败的次数。 | JS API 调用失败次数/JS API 调用总次数。 | 小程序使用过程中,页面平均渲染帧率。 |
3、标准
以上小程序相关指标的数据开发者可以在登陆抖音小程序后台后,点击“开发”->“性能分析”->“性能概览”中的概览页查看各指标近 90天 的数据(对于 pv 低于 10000 的小程序相关数据可能不够准确,仅供参考);每个指标都有相关的图表展示,鼠标移入图表时,会展示目前抖音小程序相关指标在小程序大盘的平均值。
最低标准:开发者是确保自己的小程序的各个指标的数据都优于大盘数据;
具体标准:
指标名 | 计算公式 | 标准 | 说明 |
代码执行耗时 | 按 duration 求#80%分位数 近似计算 | <150ms | 开发者代码执行耗时,主要包含APP.js 及其引用文件的执行耗时 |
First Paint(FP) | 按 duration 求#80%分位数 近似计算 | (0, 1s] | 从打开小程序到首次 DOM ready 的耗时(渲染到浏览器前) |
First Meaningful Paint(FMP) | 按 duration 求#80%分位数 近似计算 | (0, 2s] | 首次最大内容绘制耗时 |
JsError 异常率 | 影响用户数/uv | < 20% | 因为开发者 js 错误受到影响的用户数与小程序总 UV 的比值 |
启动各阶段到达率 | FP到达率:首次 DOM ready PV/总 PV FCP到达率:首次最大内容绘制 PV/总 PV | FP到达率:98% FCP到达率:96% | 到达率是反映用户对小程序的性能你及功能认可度的指标 |
页面白屏率 | 白屏数 / 白屏检测总次数 | <1 ‰ | 页面白屏率包括三部分: 1,页面返回/退出白屏率 2,部分白屏由js报错导致 3,线上白屏数/PV |
异常弹窗数 | 弹窗异常次数 / 总次数 | <1 ‰ | 连续2s 内连续弹窗视为异常 |
网络请求失败数 | 请求失败数/请求总次数 | < 1% | 衡量用户服务稳定性,小程序中请求失败的次数/请求的总数 |
JS API 失败率 | 用户错误次数/请求总次数 | < 1% | 调用小程序 API 失败 |
帧率 | 按 avg_fps 求20%分位数 近似计算 | >50 | 流畅度 20分位数据 小程序使用过程中,页面平均渲染帧率 |
注:计算标准制定参考 W3C 体验指标及小程序大盘数据,可能随数据波动微调阈值。
怎么优化这些问题
1、小程序框架侧提供的优化手段
为了提升每个小程序的性能,抖音小程序从框架侧为小程序了提供了一些性能优化的手段,欢迎大家积极使用相关手段对自己的小程序进行性能优化;采用相关优化手段对小程序性能进行提成后,可以提升大家的小程序的评分,对于评分高的小程序,抖音小程序会给予相关小程序流量扶持。
主要小程序框架侧的性能优化手段有以下几方面:
1.1 数据预取
数据预取能够在小程序冷启动时提前发起请求,并缓存请求内容,在真实请求时使用缓存数据,减少网络请求的时间。在文章详情页、商品详情页等高频访问的页面,增加数据预取配置,可以提升页面完全展现的速度。通常来说,小程序获取网络数据是在运行时使用tt.request来实现的。在运行时,小程序开发者可以对用户可能触发的下一步流程去进行数据的提前获取。小程序数据预取的主要功能是在启动前,提前按照小程序配置好的规则发起网络请求并缓存,在小程序启动后直接使用缓存数据,来避免因为等待网络数据返回而展示的 loading 或页面展示不全的情况。具体接入方式请参考:数据预取
1.2 TTWebAssembly
小程序从基础库版本 2.34.0.0 开始,提供了全局的 TTWebAssembly 对象。TTWebAssembly 类似 Web 标准的 WebAssembly,在执行计算密集型任务时,与 JavaScript 相比,可以提高一定的性能。相关 API 及使用方式请参考:TTWebAssembly
1.3 框架骨架屏
骨架屏是页面的一个空白版本,通常会在页面完全渲染之前,通过一些灰色的区块大致勾勒出轮廓,待数据加载完成后,再替换成真实的内容。通过小程序框架提供骨架屏机制,能比业务中创建的骨架屏加载时机更靠前,使用这一机制,可以减少用户的白屏等待时长,给用户带来更好的体验。具体接入方式请参考:骨架屏
1.4 分包加载
小程序包体积是影响小程序启动速度的一个主要原因,开发者可以将小程序划分成不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载,具体接入方式可以参考:分包加载
2、开发者层面可完成的优化手段
2.1 场景问题
- 高并发问题:抖音小程序相比于其他平台存在一些特殊小程序入口,如抖音直播间的讲解卡,小雪花等(详细入口示例见体验一致性问题);这些场景下的入口使得小程序的瞬时流量会比其他平台大很多;开发者在对小程序设计之初需要对小程序的用途,及使用到的小程序接口有一个大致评估,对于类似于商品抢购这样的这种的订单接口需要在进行活动前提前做好扩容处理,来避免高并发引起的问题。
- 体验一致性问题:开发者需要对目前小程序在抖音上挂载的小程序入口有一个清楚的把控,并且在配置小程序的启动页面时需要进行仔细核对,确保每个小程序入口的配置正确,符合自己和用户的预期。具体的入口配置说明可以参考:流量入口配置说明、入口场景值
2.2 通用启动问题
2.2.1 加载速度慢优化手段
(1)减小包体积
启动性能优化最直接的手段是降低代码包大小,代码包大小直接影响了下载耗时,影响用户启动小程序时的体验。开发者可以采取以下手段优化代码包体积:
- 使用分包逻辑:对于所有类型的小程序而言,使用分包加载是优化小程序启动耗时最有效的方法。建议开发者按照功能模块,将小程序的页面按使用频率和场景拆分成不同分包,实现用户在使用小程序时,不同页面的代码包按需加载。具体接入方式请参考:小程序分包
- 优化资源文件:小程序项目中免不了使用图片、音频、视频、字体等资源文件,这些资源文件通常难以被进一步的压缩,因此会占用较多代码包体积,对于下载耗时的影响比代码文件大得多。据开发实践经验,建议开发者:
- 对于不需要透明格式的图片,推荐采用 jpeg 格式来代替 png 格式。同时使用适当的压缩工具对图片进行压缩,如 pngquant-bin 或者 mozjpeg 。
- TTSS 、 TTML 等文件中,尽量避免使用 base64 内联过多、过大的图片等资源文件,建议把这些图资源文件通过外链的方式引入。
- 音频、视频、字体等资源文件的压缩有限,建议尽量避免放到代码包中,或部署到 CDN ,通过外链方式使用。
- 大图建议尽量避免放到代码包中,或部署到 CDN 且启用并发加载数量控制,启用 HTTP 缓存功能,下次加载同样路径的图片,将直接从缓存读取,不再消耗网络带宽。
- 较小的图片(例如小于 10KB)建议直接放到代码包中或整合成雪碧图,避免不必要的小图片的请求耗时影响小程序启动耗时。
- 开启图片懒加载,设置 lazy-load 属性,避免屏外图片的请求影响小程序启动耗时。
- 清理无用内容:除了 工具默认忽略的文件 外,小程序打包会将工程目录下所有文件都打入代码包内。推荐开发者定时对下列类别的内容进行清理,以降低代码包体积:
- 在开发阶段意外引入的第三方库;
- 版本迭代升级废弃的老旧代码或无效依赖;
- 与线上产品环境无关的测试代码;
- 声明但未使用的组件、插件、扩展库。
在使用三方框架或者打包工具(如、 Webpack、Rollup 等)对小程序代码进行预处理时,可以利用框架、工具提供的 tree-shaking 等特性去除冗余代码。同时,也要注意避免在预处理阶段时引入不必要的库和依赖。
2.2.2首屏渲染慢优化手段
(1)减少同步逻辑
逻辑线层 initData(初始数据) 发送给视图线层后,视图线层才能开始进行渲染页面的工作。如果 initData 发送过晚,会导致渲染页面流程滞后。为了保证启动速度,开发者应该尽可能减小加载小程序代码包到发送 initData 过程的耗时。具体有以下手段:
- 避免启动时运行过多同步代码。启动流程中,会注入开发者代码并顺序同步执行App.onLaunch,Page.onLoad, 因此在这些前置的生命周期中应该减少使用同步 API 及过多的同步逻辑;
- 与渲染数据无关逻辑优先使用异步 API;
- 减少启动过程中无用代码的加载。对于启动过程中使用不到的 JS, 可以先不加载,在真正需要使用时再去加载;
- 充分利用缓存。对调用频次高的方法的结果进行缓存,例如对于 tt.getSystemInfo, tt.getSystemInfoSync 的结果应进行缓存,避免重复调用。
(2)优化网络请求耗时
很多小程序在渲染首页时,需要依赖服务端的接口数据(如商品列表等),在请求数据未成功返回时,小程序的首页可能是空白或者骨架屏。想要减少这部分空白或者骨架屏时间需要开发者对所使用到的网络请求进行有效的优化,具体有一下几种手段:
- 将首页依赖数据的网络请求进行有效合并,同时优化网络接口的速度,使得更快的数据返回,网络请求需要相对较长的时间,我们建议开发者在 Page.onLoad 或更早的时机发起网络请求,而不应等待 Page.onReady 之后再进行;
- 建议使用小程序的数据预取能力;
- 避免非必要的 reLaunch,尽可能保证页面的及时展示;
- 缓存请求数据,小程序提供了 tt.getStorage、tt.setStorage 等读写本地缓存的能力,数据存储在本地,返回的会比网络请求快,如果开发者基于某些原因无法采用数据预拉取,我们推荐优先从缓存中获取数据来渲染视图,等待网络请求返回后进行更新。
(3)优化图片资源
很多小程序为了让小程序展示的信息更丰富、更具吸引力,会在首页使用图片,但是图片加载过慢可能导致页面部分白屏,推荐以下手段进行优化:
- 使用 CDN 并开启图片缓存,开启 HTTP 缓存控制后,下一次加载同样的图片,会直接从缓存读取,大大提升图片加载速度;
- 进行合理的压缩,图片过大会延迟页面的完全渲染;
- 选择合适的图片格式,对于不需要透明格式的图片,推荐采用 jpeg 格式来代替 png 格式。
(4)优化页面结构
渲染线程收到主页面数据后会进行页面更新,首屏渲染完成的时机和页面的结构有很大的关系。为了保证启动的速度,开发者应该尽可能地降低渲染时长。具体有以下优化手段:
- 延迟渲染首屏可视区域之外的区域。开发者可以将首屏可视区域外的页面进行懒加载,滑动到可视区域后再加载;
- 使用框架骨架屏,减少等待焦虑。通过小程序框架提供骨架屏机制,能比业务中创建的骨架屏加载时机更靠前,使用这一机制,可以减少用户的白屏等待时长,给用户带来更好的体验;
- 使用占位组件。在页面中渲染完成前通过占位组件替代组件进行渲染,在该组件注入后再将占位组件替换,从而降低复杂组件对页面加载的影响。
2.2.3 通用运行时优化手段
(1)合理使用 setData
setData 的过程,大致可以分成几个阶段:
- 逻辑层虚拟 DOM 树的遍历和更新,触发组件生命周期和 observer 等;
- 将 data 从逻辑层发送到视图层;
- 更新视图层虚拟 DOM 树、真实 DOM 元素并触发页面渲染。
由于小程序的逻辑层和视图层是两个独立的运行环境,不能直接进行数据共享,需要进行数据的序列化、跨线程/进程的数据传输、数据的反序列化,因此数据传输过程是异步的、非实时的。数据传输的耗时与数据量的大小正相关,如果对线程处于繁忙状态,数据会在消息队列中等待,导致处理队列阻塞,界面渲染不及时而导致卡顿,应避免无用的频繁调用。具体优化手段有:
- 合并发送数据,频繁的数据更新会导致页面不断重新渲染,导致渲染线程繁忙,进而阻塞交互事件的响应;
- 不发送与页面渲染无关的数据,setData 是作为页面渲染数据更新的来源,与页面渲染无关的数据可以存储到本地或挂载到页面或全局对象;
- 处于后台不调用,后台态页面的渲染用户是无法感受的,另外后台态页面去 setData 也会抢占前台页面的执行,造成不必要的性能开销;
- 不在高频事件中调用,如 scroll 事件中;
- 动画减少使用 setData,优先使用 CSS 渐变、CSS 动画。
(2)控制 TTML 节点数量和层级
节点树太大会增加内存的使用,样式重排时间也会更长,影响体验。建议一个页面 TTML 节点数量应少于 1000 个,节点树深度少于 30 层,子节点数不大于 60 个。
(3)合理使用自定义组件
- 合理拆分自定义组件。Page 中的 setData 会触发渲染层以页面级别进行 diff 操作,如果页面比较复杂且没有使用自定义组件,那么 diff 的成本会很高,导致体验比较差(更新卡顿、不粘手等感受)。如果页面转换为若干个组件,组件中 setData, 只会触发渲染层对应组件的 diff 操作,diff 成本会降低很多,使用体验也会更优。但自定义组件的使用会引入前置的注册和自定义组件生命周期执行逻辑,因此建议将业务逻辑比较独立或变更频率较大的部分进行拆分,避免盲目拆分;
- 只注册当前使用的组件。为了保证自定义数据在不同的页面实例中也是不同的实例,小程序框架会在页面创建时对这部分数据(函数类型字段除外)做一次深拷贝,如果自定义数据过多或过于复杂,可能带来很大的开销。因此建议在usingComponents中应只注册当前页面有使用到的自定义组件;
- 使用同步修改 data 的能力。在app.json 中增加配置component2: true 后,支持在 created 生命周期中修改自定义组件初始数据,自定义组件将在created 生命周期执行完成后开始渲染。开启后有以下优化点:能够很好的解决依赖计算逻辑导致的 data 频繁变更。避免初始数据依赖大量计算逻辑时,由于 data 变化导致页面渲染内容闪动或频繁变动问题优化 observer 触发方式,减少因数据变更导致的通信,提升小程序性能体验。
(4)内存优化
- 及时解绑事件绑定。事件绑定结束后,应及时解绑;
- 及时清理定时器。开发者在开发如「秒杀倒计时」等功能时,可能会使用 setInterval设置定时器,页面或组件销毁前,需要调用clearInterval 方法取消定时器。
(5)合理绑定事件
- 合理绑定和处理 scroll 事件。scroll 事件中高频执行耗时操作,会明显降低 FPS;
- 去掉不必要的事件绑定。过多的事件绑定(ttml 中的 bind 和 catch),也会增大通信的数据量和次数。渲染层会接受用户事件,如点击事件、触摸事件等。用户事件的通信过程:当一个用户事件被触发且有相关的事件需要被触发时,渲染层会将信息反馈给逻辑层。如果一个事件没有绑定事件回调函数,则这个事件不会被反馈给逻辑层。
(6)适配分屏
正常情况下页面默认是撑满整个视口的,但在直播小程序卡片、POI 详情页商品入口会出现七分屏场景。
- 通过 onResize 订阅显示区域变化。小程序支持组件和页面的生命周期函数 onResize 用于在显示区域的尺寸发生变化的时候返回当前页面的信息。其中组件需要作为页面配置到app.json 中触发事件;
- 不使用 JS 设置 ScrollView 高度。scroll-view 中分屏变为全屏时需要重新设置 scroll-view 高度,可能会出现空白区域问题,建议使用 CSS(vh)完成自适应布局。
优化后的收益
性能是用户体验中非常关键的一个部分,当性能不佳时 ,会给用户带来非常差的体验,如用户访问一个电商小程序,想购买一个商品,但是商品详情始终无法加载出来,会给用户带来焦虑感。如果是性能良好的小程序,用户可以很快地看到商品详情页,愉快地查看商品详情并下单。
具体示例:
1、西瓜视频小程序为例,在接入数据预取后,线上 FMP 从 1173ms 优化到 818ms,整体优化 300+ms,速度提升了 30%,效果显著(为了更好的对比效果,下图为 0.5 倍速的启动对比)。同时对应小程序的用户留存率提升了9.76%;
2、懂车帝小程序,在使用框架骨架屏后与自研骨架屏对比 LCP 到达率显著提升,Android 提升率达 6.79%,尤其对于页面加载复杂,LCP 到达率较低的页面,效果显著。
由此可见,性能优化不仅仅只是优化了小程序的性能数据,同时对用户体验用户留存以及用户评价都会有较大的提升,快来尝试一下这些性能优化手段吧!