微信小程序性能优化实践

历史回顾:

本文主要是根据微信小程序官方优化建议和《2018微信公开课第七季上海站·小程序专场》的性能优化方案,针对性我们的小程序项目进行性能优化实践,将过程记录下来,方便以后查看,同时也希望能帮助到其他小伙伴,做好性能优化。毕竟一切性能优化都是为了更好的用户体验。

image

小程序常见性能问题

  • 这个小程序为什么这么慢?
  • 这个小程序为什么滑不动了,卡住了?
  • 小程序在切页面的时候为什么会有延迟?
  • 为什么点击了没有反应,是不是挂掉啦?

这些问题的场景都反映了小程序的性能问题,直接影响到用户体验。

小程序如何进行性能优化?

官方建议从这两方面进行优化:

  • 启动性能优化
  • 渲染性能优化

启动性能优化

小程序在整个启动流程中,一般需要完成几项工作:

  • 1.准备运行环境(微信自己处理的)
  • 2.下载,注入并执行对应小程序代码包
  • 3.渲染小程序首页
image

开发者可以在第2,3去优化小程序的启动性能。

1.代码包大小优化

小程序在首次打开时,会去下载并执行代码包,随着代码包大小的上升,耗时也会相应增加。可以采取以下方案:

分包

image

使用分包

对开发者而言,能使小程序有更小的代码体积,承载更多的功能与服务;而对用户而言,可以更快地打开小程序,同时在不影响启动速度前提下使用更多功能。

建议开发者按照功能的划分,拆分成几个分包,当需要用到某个功能时,才加载这个功能对应的分包。

实践

我们的一个小程序在两年多前开始开发的,在设计之初,我们没有考虑到这一点,当时也没有小程序分包的功能。好吧,我们还是迎来了这个问题。

image

微信小程序在开发文档中明确指出,小程序的所有包大小必须限制在2M以内,超过大小,就算在开发者工具中都不能正常预览,更不能上传发版。解决问题的方法:

将静态资源图片压缩,因为小程序的压缩算法对图片的压缩微乎其微,于是乎,笔者对图片进行一轮压缩,并且将重复使用的图片,进行了公共提取,虽然官方推荐使用网络图片,但是还需要去维护静态资源,嫌麻烦,就放弃了。

将项目中的弃用的页面,以及不用的三方,进行了一波清除。

很多项目现在都是通过webpack打包成不同的分包,资源懒加载的形式来优化,小程序也提供了这个功能:分包,笔者按照按照功能划分的原则,将同一个功能下的页面和逻辑放置于同一个目录下,成为一个分包。

分包之后:

image

注意:1. 自定义第三方组件,需要放在主包内,miniprogram_npm文件会直接打到主包里;2. 小程序的tab切换页,必须放在主包里。

分包预下载

image

分包预下载是为了解决首次进入分包页面时的延迟问题而设计的。如果能够在用户进入分包页面之前就预先将分包下载完毕,那么进入分包页面的延迟就能够尽可能降低。

实践

用户进行了某个操作,再去下载分包,延迟操作导致用户体验很差,于是乎笔者对上面的分包设置分包预下载。在 app.json 文件中配置:

"preloadRule": {
    "pages/work/index": {
      "network": "all",
      "packages": [
        "package-work",
        "package-field-statistics"
      ]
    },
    "pages/appeal/index": {
      "network": "all",
      "packages": [
        "package-appeal"
      ]
    }
},

这里建议不要一次性把所有分包预下载,这样的操作同样回带来性能问题。

独立分包

小程序中的某些场景(如广告页、活动页、支付页等),通常功能不是很复杂且相对独立,对启动性能有很高的要求。使用独立分包,可以独立于主包和其他分包运行。从独立分包中页面进入小程序时,不需要下载主包。

image

建议开发者将部分对启动性能要求很高的页面放到特殊的独立分包中。

实践

项目中没有适合的场景,尚未实践。

2.首屏渲染优化

image

1. 提前首屏数据请求

大部分小程序在渲染首页时,需要依赖服务端的接口数据,接口请求放到页面的生命周期 onLoad 中,而不是 onReady里。

实践

监听到页面加载,就校验登录情况,请求页面数据

onLoad: function (options) {
    app.checkAuth((error, token) => {
      if (error) {
        return
      }
      // 请求该页面的数据
    })
  },

2. 缓存请求数据

小程序提供了wx.setStorageSync等异步读写本地缓存的能力,数据存储在本地,返回的会比网络请求快。

实践

登录成功后将用户的token,以及用户信息都可以缓存到本地,记得退出登录的时候清楚缓存,😂。

/**
 * 设置本地 token 缓存
 * @param {Object} session 服务器返回的数据
 * @param {String} session.access_token 存取token
 * @param {String} session.refresh_token 刷新token
 * @param {String} session.expires_in 有效期限,以秒为单位
 */
export function set(session) {
  const localSession = Object.assign({}, session, {
    expires_timestamp: getExpireTimestamp(session.expires_in)
  });
  wx.setStorageSync(SESSION_KEY, localSession);

  _token = session.access_token;
}

export function clear() {
  wx.removeStorageSync(SESSION_KEY);
  clearTimeout(refresh_timer);

  _token = null;
}

3. 精简首屏数据

推荐开发者延迟请求非关键渲染数据,缩短网络请求时延,与视图层渲染无关的数据尽量不要放在 data 中,以免传输垃圾数据,加快首屏渲染完成时间。

实践

通过id请求详情的情况,id在渲染层不需要,就可以不把id,定义在data中:

// 原来代码
data: {
    id: ‘’,
    // ….
},
onLoad: function (options) {
        this.setData({
        id: options.id
    })
    // ….
}

// 改写后 不把id定义到data中
data: {
    // ….
},
app.checkAuth((error, token) => {
      const id = options.id === undefined ? '' : options.id;
      this.id = id 
})

接口返回的数据要做数据处理,不要直接都塞给data,减少冗余数据的双线程回传。也是 精简首屏数据优化的一部分。

4. 避免阻塞渲染

在小程序启动流程中,会顺序执行app.onLaunch, app.onShow, page.onLoad, page.onShow, page.onReady,所以,尽量避免在这些生命周期中使用Sync结尾的同步API,如 wx.setStorageSync,wx.getSystemInfoSync 等。

实践

项目中没有这样使用,有先见之明。😄

渲染性能优化

小程序的视图层目前使用 WebView 作为渲染载体,而逻辑层是由独立的 JavascriptCore 作为运行环境。在架构上,WebView 和 JavascriptCore 都是独立的模块,并不具备数据直接共享的通道。当前,视图层和逻辑层的数据传输,实际上通过两边提供的 evaluateJavascript 所实现。即用户传输的数据,需要将其转换为字符串形式传递,同时把转换后的数据内容拼接成一份 JS 脚本,再通过执行 JS 脚本的形式传递到两边独立环境。

而 evaluateJavascript 的执行会受很多方面的影响,数据到达视图层并不是实时的。

image

** 常见的 setData 操作错误 **

image

1. 频繁的去 setData

导致了两个后果:

  • Android 下用户在滑动时会感觉到卡顿,操作反馈延迟严重,因为 JS 线程一直在编译执行渲染,未能及时将用户操作事件传递到逻辑层,逻辑层亦无法及时将操作处理结果及时传递到视图层;
  • 渲染有出现延时,由于 WebView 的 JS 线程一直处于忙碌状态,逻辑层到页面层的通信耗时上升,视图层收到的数据消息时距离发出时间已经过去了几百毫秒,渲染的结果并不实时;
image

实践

目前项目代码还是比较规范的,我们并没有把setData当成一个普通的对象去调用,晓得每次使用都需要两个线程间通信,WebView再去渲染的。哇,好棒。

2. 每次 setData 都传递大量新数据

由setData的底层实现可知,我们的数据传输实际是一次 evaluateJavascript 脚本过程,当数据量过大时会增加脚本的编译执行时间,占用 WebView JS 线程。

image

实践

目前每个接口的数据量并大,数据的量级还没达到影响脚步执行的程度,有需要的话再优化吧。

3. 后台态页面进行 setData

当页面进入后台态(用户不可见),不应该继续去进行setData,后台态页面的渲染用户是无法感受的,另外后台态页面去setData也会抢占前台页面的执行。

image

实践

A页面上有个定时器,此时打开了B页面,A页面的定时器还在运行,继续抢占B页面的资源,B页面卡顿了,但是并不是B页面的造成的性能问题,这种问题就不太好排查。希望大家都能做个有始有终的人,定时器不用了要清除。下面demo,定时器在 onHide 时要清除掉。切记切记👇

/**
   * 生命周期函数--监听页面显示
   */
onShow: function () {
    clearTimeout(getTodaytime)
    this.updateNowTime()
},

/**
   * 生命周期函数--监听页面隐藏
 */
onHide: function () {
    // 取消定时器 防止小程序内存不足,崩溃
    clearTimeout(getTodaytime)
},
updateNowTime() {
    getTodaytime = setInterval(() => {
      const myDate = new Date(); 
      const hours = myDate.getHours())
      const minutes = myDate.getMinutes())
      const seconds = myDate.getSeconds())

      const newTime = hours + ':' + minutes + ':' + seconds;
      this.setData({
        newTime: newTime
      })
    }, 1000)
  },

2. 用户事件使用不当

  • 过多的使用bindTap、bindCatch
  • 不当的使用onPageScroll
image

实践

项目中展示没用使用该事件。

3. 使用自定义组件

在需要频繁更新的场景下,自定义组件的更新只在组件内部进行,不受页面其他部分内容复杂性的影响。

image

实践

我们项目小程序打卡功能,需要展示当前到时间:时分秒,此处用定时器实现的,需要频繁的更新setData,此处就适合将该定时器提取为组件,让其在组件内部数据更新,不影响页面的其它部分。

总结

image

小程序启动加载性能:

  • 控制代码包的大小
  • 分包加载
  • 首屏体验

小程序渲染性能:

  • 避免不当的使用setData
  • 合理利用事件通信
  • 避免不当的使用onPageScroll
  • 优化视图节点
  • 使用自定义组件

注:文中PPT来自《2018微信公开课第七季上海站·小程序专场》

参考

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,383评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,522评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,852评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,621评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,741评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,929评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,076评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,803评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,265评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,582评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,716评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,395评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,039评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,027评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,488评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,612评论 2 350