小程序redux性能优化,提升三倍渲染速度

栏目: IOS · Android · 发布时间: 7年前

内容简介:最近用户反馈我们的小程序很卡,打开商品列表需要四五秒时间,带着这个疑问,我决定对小程序做个全面的性能优化,要做性能优化,必须先理清以下三个关键点。在阅读案例分析前,建议能先了解小程序的工作原理和性能关键点。小程序的视图层目前使用 WebView 作为渲染载体,而逻辑层是由独立的 JavascriptCore 作为运行环境。在架构上,WebView 和 JavascriptCore 都是独立的模块,并不具备数据直接共享的通道。当前,视图层和逻辑层的数据传输,实际上通过两边提供的

最近用户反馈我们的小程序很卡,打开商品列表需要四五秒时间,带着这个疑问,我决定对小程序做个全面的性能优化,要做性能优化,必须先理清以下三个关键点。

  1. 产生性能问题的关键点
  2. 度量性能指标
  3. 寻找解决方案

在阅读案例分析前,建议能先了解小程序的工作原理和性能关键点。

工作原理 (官方说明)

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

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

性能关键点(官方说明)

1. 频繁的去 setData

在我们分析过的一些案例里,部分小程序会非常频繁(毫秒级)的去 setData ,其导致了两个后果:

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

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

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

3. 后台态页面进行 setData

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

度量性能指标

我们在优化性能时,指标是非常重要的,没有指标,你没法知道优化的点是否有效。不能单凭感觉去优化,要根据指标反馈,明确优化的成果。同时,优化就像个无底洞,要注意投入产出比。

用户反馈的卡顿,要么就是js执行消耗资源过多导致处理器没响应,要么是UI渲染消耗资源过多,导致UI没法响应用户操作。

通过查看代码,我们并没有消耗大量计算资源的业务逻辑,但是出现了UI反复操作和抢占资源的现象。

如何度量

可以利用setData的第二个参数,传入callback函数,统计渲染时长。代码如下

let startTime = Date.now()
this.setData(data, () => {
    let endTime = Data.now()
    console.log(endTime - startTime, '渲染时长')
})
复制代码

案例分析

检查点:是否频繁去setData

检查结果:存在

产生原因:redux中监听的是整个store,只要store变化,就会执行setData操作,这就意味着页面无关的数据改变,也会触发该页面执行setData操作,但是这个操作是无意义的。

问题代码:

// libs/redux-wechat/connect.js

// 对整个store进行subscribe。变化就执行handleChange
this.unsubscribe = this.store.subscribe(handleChange.bind(this, options));

function handleChange(options) {
    ...省略代码
    const state = this.store.getState()
    const mappedState = mapState(state, options);
    this.setData(mappedState)
}

复制代码

解决方案:

  1. 只监听当前页面用到的store中的部分数据,只有该部分数据变化,才setData。(store没提供单个数据的监听,如果自己修改redux实现,难度较大,同时修改太底层,容易出不可预料的异常。)
  2. 判断页面数据与需要更新数据是否相同,如果相同,不做操作。(这个方案成本比较低,就用它吧)

代码实现:

// libs/redux-wechat/connect.js
// 如果更新的数据和页面数据相同,不做操作。
function handleChange(options) {
    ...省略代码
    const state = this.store.getState()
    const mappedState = mapState(state, options);
    // 如果更新的数据和页面数据相同,不做操作。
    if (mappedState === this.prevState) return // 新加入代码
    this.setData(mappedState)
    // 保存上一次数据
    this.prevState = mappedState // 新加入代码
}
复制代码

另外一个优化:如果store数据毫秒级变化怎么办,例如更新购物车的同时,还更新了购物数量,能不能把两次变化合并起来?因为store的数据是共享的,最后一次的更新就是最新的数据,可以采用节流起对请求进行合并。

clearTimeout(this.setDataTMO)
      this.setDataTMO = setTimeout(() => {
        this.setData(mappedState)
      }, 50); // 时间可以看情况调整
复制代码

检查点:每次 setData 都传递大量新数据

检查结果:存在

产生原因:

  1. 页面存在引用没用到的store数据。
  2. 后端返回数据直接进入store,后端接口返回冗余字段。

问题代码:

/pages/user/index.js
connect(state => ({
    member: state.member,
    mycoupon: state.mycoupon,
    guessLikeList: state.recommend.guessLikeList,
    locationInfo: state.common && state.common.locationInfo, //可删除
    selectedseller: state.home.selectedseller,//可删除
    carts: state.carts.carts,//可删除
    ...state.common
  }))
复制代码

解决方案:

  1. 删除页面无用的connect (老业务在使用,修改存在风险,通过后续迭代优化)
  2. 请求后端接口后,拿到数据进行优化处理再把数据传入store(成本较高)

检查点:后台态页面进行 setData

检查结果:存在

产生原因:redux connect设计与小程序有差异

问题代码:

// libs/redux-wechat/connect.js
    function onLoad(options) {
      ...省略部分代码
      if(shouldSubscribe){
        this.unsubscribe = this.store.subscribe(handleChange.bind(this, options));
        handleChange.call(this, options)
      }
    }
    function onUnload() {
	  ...省略部分代码
      // 页面onUnload时,才解除监听
      typeof this.unsubscribe === 'function' && this.unsubscribe()
    }
复制代码

小程序生命周期中,onUnload会在页面销毁时执行,例如A->B->C->D 的跳转,A页面一直在监听store的变化,如果D页面修改数据,会造成A,B,C页面也执行setData操作,抢占了D的资源,因此造成卡顿。

解决方案:

  1. 后台状态的页面在setData时直接return(目前采用该方法)
  2. 当页面隐藏时,移除监听。

代码实现:

// 因为在后台的页面setData会抢占前台资源,所以在后台的页面不要执行setData操作
if (this.route !== _getActivePage().route) return
复制代码

但是由于在后台的页面数据没法更新,如果D页面修改A引用的数据,就会出现A引用旧数据问题,所以在onShow的时候做一次同步。

// 后台的页面切换到前台的时候,做一次数据同步
    function onShow(options) {
      if(shouldSubscribe){
        handleChange.call(this, options)
      }
      if (typeof _onShow === 'function') {
        _onShow.call(this, options)
      }
    }
复制代码

指标测试

做了这么多,到底有没用,拿出来溜一溜就清楚了。

测试平台:iphone7、三星s7 、小程序开发工具

测试流程:首页 -> 配送到家 -> 加入购物车 -> 结算 ->查看订单

测试指标:调用setData次数,渲染总耗时,平均单次渲染耗时

未优化指标:

平台 setData次数 渲染总耗时(ms) 平均单次渲染耗时(ms)
三星s7 204 250258 1226
iphone7 167 38260 229
小程序开发工具 193 36811 190

优化后指标:

平台 setData次数 渲染总耗时(ms) 平均单次渲染耗时(ms)
三星s7 28 11227 400
iphone7 28 3971 141
小程序开发工具 31 2489 80

差异对比:

平台 setData次数差 渲染总耗时差(ms) 平均单次渲染耗时差(ms)
三星s7 176 239031 826
iphone7 139 34289 88
小程序开发工具 162 34322 110

总结:

  1. 优化后setData次数平均下降150次。
  2. 渲染耗时越是卡顿的机器,收益越大,三星s7平均每次渲染耗时降低826ms。

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

A Philosophy of Software Design

A Philosophy of Software Design

John Ousterhout / Yaknyam Press / 2018-4-6 / GBP 14.21

This book addresses the topic of software design: how to decompose complex software systems into modules (such as classes and methods) that can be implemented relatively independently. The book first ......一起来看看 《A Philosophy of Software Design》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换