百度小程序性能优化
引子:
一个指标引发的血案
https://www.cnblogs.com/Sherlock09/p/11726885.html
性能优化
进入移动互联网时代,传统Web开发技术(HTML,CSS,JavaScript)风光不再,客户端技术(iOS以及Android)依靠良好的体验重新崛起。但是客户端技术的开发效率始终无法与Web技术抗衡,同时会受到诸多平台层面的限制。在这一大背景下,小程序独特的架构诞生了,它将Web前端技术与传统的客户端技术结合在一起,其目的是在开发效率上超过传统的客户端技术,在使用体验上超越传统的Web前端技术。由于小程序的架构区别于传统的Web前端技术,开发者在开发过程可能会遇到一些性能上的问题。本文旨在介绍百度小程序一些实现原理和优化手段,帮助开发者优化自己的小程序。
一 小程序运行时简介
在传统的Web前端项目中,所有代码全部运行在浏览器中。而小程序提供的运行环境有两种,分为逻辑层和视图层。假设现在开发者的小程序项目中有两个页面pages/index和pages/home
,那么逻辑层代码指的是app.js
与pages/index/index.js
还有pages/home/home.js
,视图层代码指的是pages/index/index.swan
和pages/home/home.swan
。在小程序中想要改变视图需要逻辑层与视图层之间进行通信,这部分通信是需要客户端参与,会消耗一定的系统资源。
存在问题:setData 调用随意、频繁,有很多不必要的数据没有必要存放在data中
setData操作的优化
setData方法是开发者通过逻辑层向视图层发送数据的方法。每一次 setData 的调用,都会触发一次通信,而每一次的通信都会消耗一定的系统资源,因此,开发者在使用 setData 需要注意以下几点:
- 不要过于频繁调用setData,应考虑将多次setData合并成一次setData调用。
- 不在视图层使用的数据不要通过setData传输。
- 不在页面不可见之后使用setData.
不建议在更新数据结构当中的某一子项的时候将整个数据结构放到setData方法中,可以通过优化setData的key值来实现。
错误写法:
let person = this.getData(‘person‘);person.age = 30;this.setData(‘person‘,person);
正确写法:
this.setData(‘person.age‘, 30);
在更新列表中某一项内部的值时,推荐的用法为:
this.setData(‘list[0].person.name‘, ‘Harry‘);
在处理无限滚动页面加载的时候,我们发现很多开发者将新的一页上的数据添加整体的数据里面再调用setData。这样做造成每次页面加载传输的数据越来越大。
未优化情况下的做法:
let pages = this.data.pages.push(pageData)this.setData({ list: pages});
优化后的做法:
Page({ data: { pages: [], // 使用一个二维数组来描述长列表 currentPage: 0 // 当前页面的页码 }, onReady() { // 如果需要更新一页的数据,那么直接更新二维数组中的项 this.setData(`pages[${currentPage}]`, pageData); }});
使用trackBy来优化列表更新时的渲染性能
当使用下拉刷新功能时,新的数据会被添加到当前列表的头部,这种情况下,页面中列表内所有的项都会被重新渲染一次。
// 下拉刷新更新方式举例let list = list.unshift(newPage);this.setData({ list});
如果使用trackBy,那么原先的列表内的项位置会移动,新添加的项会被渲染。这样可以省去一部分重新渲染带来的消耗。
// 使用trackBy举例<scroll-view> <view s-for="item, index in list trackBy item.id"> </view></scroll-view>
优化:
1. 由于代码中的setData 会增加逻辑层与渲染层间的线程通信,所以要避免频繁的调用setData,将相应的调用合并
2. 对于页面中不涉及渲染的变量,从data中拆分出来(待),scene这些公用的数据可以放在初始化的app.js中
3. 小程序版本和api 更新迭代快,废弃掉原来旧的影响性能的api,例如getData这个api,在之前的版本都是可以用的,在新的版本中虽然也可以用,但是由于对性能有一定影响,所以
遵循优化的原则,改掉这些对性能有影响的api
二 包体积优化
减小包的体积可以减少包的下载时间。根据已经上线的小程序包的统计分析结果,小程序官方将主包的体积控制在 1M 左右,包内的文件个数限制在 200 以内。除了体积之外,小程序包内的文件个数也直接影响到小程序包的解压速度。因此,需要减少小程序包内非必须的图片、字体、音频等资源的文件个数。同时,逻辑层与视图层的代码都需要加载到 webview 实例当中去,减小这部分的体积也会加快小程序的启动速度。
存在问题:包体积过大,目前主包大小在1.44M
优化方案:
1. 分包加载
分包加载是智能小程序用来解决包体积过大而给出的一个技术解决方案(点击查看分包加载相关文档)。为了最大程度的减少主包的大小,提高小程序的加载速度,开发者使用分包加载策略时,建议将必须的和经常访问的页面放入主包当中,例如将声明在 app.json 当中的 tabBar 配置项下的页面放入主包当中。另外,根据小程序的投放场景不同,开发者需要仔细思考自己的小程序中哪些页面是经常被访问的。举个例子,在Feed和搜索分发的小程序非首页页面我们建议放到主包中,避免首次打开投放页面处于分包内时需要先下载主包再下载分包而导致的性能退化。
{ "pages": [ // 该配置项下经常要访问的页面放入主包当中,其余页面放入子包当中 "pages/index/index", "pages/detail/detail" ], "tabBar": { // 该配置项下面的页面建议放入主包当中 "list": [ { "pagePath": "pages/index/index", "text": "首页" }, { "pagePath": "pages/logs/logs", "text": "日志" } ] }} |
2.图片的优化
- 原则上除小程序 icon 以外的图片资源均应部署到 cdn 上,不建议把所有的图片都放在小程序包内,这样会增大包的体积。影响小程序包的下载速度与解压速度。
- 过大的图片在加载时会消耗更多的系统资源。所以建议开发者尽量不使用超过 300K 的图片资源。
- 对小程序包内的图片选择合适的格式进行存储,不需要透明格式的图片,推荐采用 jpeg 格式来存储代替 png 格式。
- 适当的降低图片的质量,大多数场景我们并不需要 100% 的 JPEG 压缩比例,此时我们可以修改 JPEG 的压缩比例从而大幅减小 JPEG 图片的体积。例如:100% 的 JPEG 图片与 25% 压缩比大小差为 90%,但是肉眼可见的感知可能微乎其微。
- 对小程序包内的图片进行适当的压缩,对于 png 格式的图片,最常用的工具是tinypng。对于jpeg格式的图片,可以使用的工具是tinyjpg。也可以使用EXIF Purge或者其他图片编辑软件来清除图片的exif信息,减小图片的体积。
- 去除小程序包内冗余和无用的图片资源,例如:重复的图片,未引用或不需要的资源文件等。
- 安卓端支持webp的图片格式,webp图片格式在有损压缩的情况下,肉眼不易察觉出压缩前后的变化,但是体积却得到很大的减小。需要注意的是,iOS平台下的小程序不支持webp格式,如果开发者要使用webp格式的图片的话,需要注意区分平台。
3. 图片组件懒加载
其他资源文件的优化
JSON描述文件可以通过jsonminify工具对JSON文件进行压缩。
三 请求数据的优化
绝大多数小程序都需要请求服务端来获取渲染页面的数据,对于请求数据的优化,总结起来就是一句话,关键的早请求,不关键的晚请求。
涉及到关键数据的异步请求可以尽早的发出,不需要等待页面的 onReady 生命周期之后才去发送请求。这样可以让页面所需的数据尽可能早的处于 Ready 状态。除了在现有的生命周期发送数据请求以外,我们还提供了prefetch机制(prefetch机制需要在app.json之中进行配置)能够在小程序框架启动阶段就去请求数据,而不用等待页面生命周期触发。
根据小程序被打开的场景,可以对异步请求进行优先级排序,不重要的请求放在页面的 onReady 生命周期去请求。例如,贴吧小程序最经常被访问的页面是帖子内容页,因此除了当前帖子内容以外的数据请求都是非关键请求,可以将触发的时机延后,保证帖子内容尽可能早的被加载出来。
四 清理定时器
当使用swan.navigateTo
进行页面跳转的时候,旧页面是没有被销毁的。旧页面当中定义的定时器仍旧会运行。因此在页面跳转的时候,一定要记住清理没有用的定时器:
Page({ onReady() { this.timer = setInterval(() => { // do something }, 300); }, onHide() { // 在页面不在前台显示的时候,清除无用的定时器 this.timer && clearInterval(this.timer); }}) |
五 合理使用自定义组件
自定义组件与模板内的import与include功能都可以达到代码复用的效果。需要注意的是,如果自定义组件内没有逻辑层的功能的话,这时候使用自定义组件就是非必须的了。我们可以用下面的方式实现代码的复用:
<import src="./person.swan" /><view class="container"> <!-- 使用import复用模板代码 --> <template is="person-card" data="{{person}}" /></view> |
// Person相关函数export function play() { /* do something*/ }export function eat() { /* do something*/ }export function sing() { /* do something*/ } |
// 复用person.js中的函数import * as Person from ‘./person‘;Page({ onReady() { this.play(); this.eat(); this.sing(); }, onShow() {}, onLoad() {}, onHide() {}, ...Person}) |
六 渐进式加载l
提前加载页面的骨架,可以减少用户的白屏等待时长,百度智能小程序提供了渐进式加载机制,使用这一机制,可以给用户带来更好的用户体验。下面将介绍如何使用这一机制为开发者自己的小程序提供渐进式加载的能力。
这里插一句额外的,在其他webpack的项目中,也可以使用page-skeleton-webpack-plugin,这个插件来生成骨架屏,具体可参考
https://github.com/ElemeFE/page-skeleton-webpack-plugin
第一步:在工程项目根目录新建skeleton文件夹(除了config.jso以外的文件目录可自定义名称),目录如下所示
skeleton|--- page/| |--- index.tpl 骨架屏模板代码| |--- list.tpl 骨架屏模板代码|___ config.json page和骨架屏的映射关系
第二步:使用标准HTML与CSS,编写骨架屏模板文件,如index.tpl骨架屏代码如下图
<style>.skeleton-list { background: gray;}</style><div style="width:100%"> <ul class="skeleton-list"> <li></li> <li></li> <li></li> <li></li> </ul></div>
第三步:配置config.json文件,pages和骨架屏是多对一的映射关系,可配置多个页面对应同一个骨架屏模板
{ "pages/home/index": "skeleton/page/index", "pages/list/index": "skeleton/page/index"}
说明
1. 需要目前最新的开发者工具rc版本与百度App 11.10及其以上版本 2. 骨架屏移除的时机由开发者自己掌控。开发者可以在Page内通过调用this.removeSkeleton()移除。
七 白屏优化
当前首页白屏率
排查异常
小程序白屏数据出现异常上涨时,可以从以下三个方面着手排查分析:
服务稳定性
- 小程序页面数据请求是否正常:
通过线上巡检,发现有小程序存在自身服务不稳定的情况。例如小程序页面数据请求返回4XX,5XX错误等。 - HTTPS证书是否存在问题:
排查HTTPS证书是否已过期,导致小程序相关请求失败,无法展示数据。
有些小程序可能误使用了自签的HTTPS证书,由于无法被信任,用户也无法强制信任,导致页面数据获取失败。
业务逻辑
有些小程序的页面数据展示可能存在前置条件,例如需要登录、定位等。在条件不满足时,可能存在兼容处理问题。这里给出常见的几种case:
- 页面打开时需要首先进行授权,获取权限:
授权失败时需要有响应的兼容逻辑或者给予明确提示。 - 页面打开时需要登录才可展示内容:
例如常见的购物类小程序,用户未登录时需要有相应的提示,以及触发登录的按钮或者入口。 - 网络连接失败时,页面兼容性不足:
这种情况最好是有对应的错误页和重试入口,保证用户可再操作,提供自主恢复的能力。 - 逻辑中存在自设校验,校验不通过:
有些小程序是从微信小程序迁移而来,内部逻辑中可能存在自设的平台检测校验等,迁移时或者版本更新时没有同步变更,导致校验不通过,从而导致页面异常。
框架兼容性
小程序框架自身也在不断更新,所支持的能力也在不断更新和扩充。同样,开发者也会对小程序自身也会进行版本更新。这里就涉及到了兼容性问题。小程序框架版本修复Bug记录和版本兼容性,请参考以下连接了解和主动规避:
优化性能和体验
已有启动性能数据,平均数据和80分位数据较快不一定能保证白屏率就低,白屏case大概率发生在性能的长尾数据中。
从平台跟进的多个小程序白屏数据分析结果来看,小程序白屏率高的主要因素是页面数据加载和渲染较慢。如果小程序上线后白屏数据就处于高位,或者版本更新后白屏数据上涨,可以通过以下方面进行分析和优化:
- 页面结构:
部分小程序的页面内容重度依赖于服务器的返回,在服务端没有数据返回的时候,页面没有任何内容展示,这样的情况在遇到网络波动或者服务发生抖动的时候会造成白屏率的陡增。开发者可以在服务端数据返回之前通过动画,文案体验上的优化来减小白屏率。 页面数据加载方式:
针对一次请求返回的数据过多的情况,可以从两个角度来优化:1 、非关键数据延迟请求,2、非关键数据延迟渲染
非关键数据延迟请求:swan.request({ url: ‘https://www.baidu.com/keyData‘, success: res => { this.setData({ keyData: res }); swan.request({ url: ‘https://www.baidu.com/nonKeyData‘, success: res => {} }); }})
非关键数据延迟渲染
this.setData({keyData}, () => { this.setData({nonKeyData});});
增加过渡态提示:
页面加载时,可以使用Loading组件等形式进行提示,给用户一个提示,提升用户体验。使用骨架屏:
骨架屏形式类似下图,可以很好的提升用户使用小程序时的体验。- 默认态处理:
例如在涉及到定位时,部分小程序会等待定位完成后才展示数据,可以增加默认态数据,定位完成后再更新页面数据。
总结:
上线四五天后,整体效果有了一定的提升,由于用户收敛和老用户更新版本的影响,最终的效果还需再观察一段时间
通过这次性能优化的调整,体会到了小程序这种运行在端内的代码对于性能的苛刻要求,你的每一部分代码的简洁程度,代码组织的调整,静态资源大小、部署、加载的策略
都深深影响到小程序加载的整体指标。
同时,日常生活中的代码,也有同样的问题
1. 对于新的代码,是否做到了合理的书写?拆分?便于维护?
2. 对于冗余的代码,实际上严重影响着代码整体加载效率,是否有目的的清理和调整?
3. 对于静态资源,一些图片、字体,图片有没有压缩合并?冗余的一张图片有可能比冗余的一个jquery更大?
4. 对于旧的api,拿vue2.0举例,在v-for的时候,如果把key添加上,将大大提升Vnode diff的效率,这块是否有意识的去优化
5. 很多东西,实际上我们都知道应该去做,是否去主动的做一些,哪怕做一点,哪怕这次去一个 没用 || 已经 改版了的图片、js、css(主要)
注:由于内部安全红线,相关业务信息已隐藏,转载请著名出处(https://www.cnblogs.com/Sherlock09/p/11726885.html)