文章

项目经验

长列表优化(虚拟列表)

背景

  • 说说采用分页的形式加载,每个组件加载后不释放,导致内存很容易占用过高,从而崩溃,在ios系统的表现为滑动出现黑屏,闪退。

解决方法

  • 要解决的问题

    • 要知道哪些说说在视口内,从而渲染这些说说
    • 防止窗口抖动,页面塌陷
  • 核心思路

    • 一个list存所有数据、vlist存真实渲染的数据,vlist通过调用list.slice方法来实现节点数量的控制
    • 找到哪些是视口内的说说,渲染他们
    • 防止顶部说说被删除后页面塌陷抖动
  • 第一次(onPageScroll + 第一个元素定位)

    • 固定每一个说说的高度,设计一个偏移值,当有说说显示全文时就增加偏移值,onPageScroll计算得到当前视口内的说说组件,定位或者margin来控制内容位置不变

      • 失败

        • 窗口非常容易抖动
        • 通过定位防抖很难实现,因为非常难计算
        • onPageScroll事件触发太频繁,即使使用防抖效果也不好,对性能消耗太大
        • 固定说说高度体验非常不好,而且偏移值难以计算,还容易出现bug,不方便日后扩展
  • 第二次(onPageScroll + 空白盒子 + 元素高度列表)

    • 不固定说说高度,而是先渲染,然后节点查询高度,将高度保存在一个全局数组中,onPageScroll计算得到哪些是要渲染的说说,解决窗口抖动不使用定位或外边距,而是将视口外的说说使用空白盒子(div)来占位

      • 解决页面塌陷的问题
      • 不限制说说的高度,方便说说组件自由扩展,没有复杂的计算
      • onPageScroll频繁调用对性能的影响问题仍未解决
      • 需要额外的维护一个全局数组
      • 说说需要先渲染一遍,获取元素高度后才能优化,虽然这样能解决内存堆积过高的问题,但是对性能会有不小的影响
  • 第三次(scroll-view + touch事件)

    • 使用scroll-view解决内容抖动塌陷,给说说进行编号,使用touch方法替代onScroll来获取视口里的说说,用户滑动说说后就会获取到改说说的编号,往前-5往后+5就是视口内的说说了 (移动端用户只能使用手指滑动,而且说说占用面积比较大,用户滑动必然会触碰到说说,触发touchend事件)

      • scroll-view组件自带防抖效果(scroll-anchor属性)
      • 不需要额外维护全局数组,不需要先渲染所有说说,极大的提高了性能
      • 不需要频繁地触发onPageScroll事件,提高性能
      • 几乎不需要计算,维护相对简单,易扩展
      • 组件提供相应的事件,比如自动滚动到某个位置
      • 滑动较快会出现卡顿 -> 需要优化
      • 用户快速上划,如果靠惯性,用户不触碰说说,则会出现到了顶部,但是顶部并不是第一条说说的bug(设计漏洞),下滑类似
  • 第四次(解决滑动卡顿)

    • 目的

      • 解决滑动卡顿问题
    • 用户感知优化

      • 加载新的一页时(此时很容易卡顿),wx.showLoading提示用户正在加载

      • 惯性上滑bug

        • 增加手势上划、返回顶部按钮
    • 性能优化

      • 优化说说组件(最终目的减少组件渲染时间,实现方法是精简说说组件)

        • touchend使用事件代理
        • 删除无用watch、uni.$on等、优化DOM结构,原则上是让说说组件更加精简
        • 提取公共逻辑:将例如删除等逻辑提取出来,说说组件只需要触发事件即可
      • 减少回流

        • 图片加载有延迟,会造成回流

          • 固定图片容器高度,使不管图片怎么加载,都只是在容器内部,不会影响到容器外部
          • 图片加载时离线,设好样式(宽高大小)后在显示(通过vshow)
    • 未解决的问题

      • 最大的问题仍未解决:即快速滑动的惯性效果会导致系统无法知道视口内是哪些说说

        • 快速下滑不能知道是否加载下一页
        • 快速上划很容易出界
  • 第五次(页面级滚动 + 定高空白盒子)

    • 优化

      • 将scroll-view组件改为view,即将scroll-view滚动改为页面滚动,因为官方说明scroll-view做长列表会有性能问题,彻底解决滚动卡顿问题
      • overflow-anchor:auto; 让位置不随内容的变化而抖动
      • 下滑的同时,在显示的说说上面添加空白盒子占位,从而解决快速上划"出界"的问题,同时触碰这些盒子时会调用事件显示说说
  • 缺点

    • 能解决一部分体验问题(比如快速上划),但是如果用户不触碰空白盒子,就无法正常显示说说,用户可能误以为是bug

    • 尝试的优化(失败)

      • 运用onPageScroll,上划时并不需要精准的定位显示到某个说说,只要大概显示就行了

        • 区分上划和下滑,上划才要处理

          • 运用touchstart和touchend事件
    • 页面滚动停止后才要执行

      • 在onPageScroll事件使用防抖,大致判断滚动停止
        • 页面上划出已有的显示的说说才执行,否则可能会导致说说错误覆盖
  • 失败,原因:onPageScroll事件中使用防抖技术来模拟监听用户滑动后页面停止滚动事件并不严谨,用户的行为难以预测,偶尔会出现bug。

  • 第六次(解决上划出现空白盒子的问题)

    • 问题:

      • 为了防止窗口抖动和页面塌陷,之前使用了空白盒子来进行占位,同时监听空白盒子,当点击盒子时会重新渲染说说列表(点击的那个空白盒子的index就是渲染说说列表的基准),这样可以解决大部分问题,但是难免会有用户滑动后不点击盒子,误以为是bug,此次优化解决此问题。
    • 解决思路

      • 监听靠近第一个说说的空白盒子的top值,在onPageScroll事件中,用页面滚动量于此空白盒子的top值比较,来判断用户是否划出了说说列表,如果划出去了,立即更新说说列表

尚存在的问题

  • 快速返回顶部会出现非常明显的卡顿(基本解决)
  • 快速上划后的空白盒子如果用户不点击,则无法更新说说列表。

websocket断线重连

问题

当小程序切换后台一段时间websocket连接会自动断开,再次切换回前台后无法正常使用。

思考

websocket连接自动断开可能是因为微信为了节省资源会自动断开,或者是手机系统休眠进程行为导致websocket连接断开。

后面发现其实是微信的限制,小程序进入后台5s后就会进入休眠,休眠30min后就会被销毁。

解决方法

运用断线重连或者心跳机制。

断线重连就是前端监听websocket连接,当发现websocket连接断开后,及时地重新连接。

心跳机制是前端每隔一段时间就发送一个消息给后端,如果后端有相应的返回,说明此时连接畅通,如果收不到回复则证明连接已断开,前端则进行重连操作。

出于资源利用率和场景适合度考虑,采用断线重连机制。

第一次(失败)

问题

使用断线重连,监听websocket连接,如果断开了,当用户重新回到界面时(onShow)重连。

重连后仍然无法正常发送消息

思考

与后端沟通测试,后端推测可能是因为websocket连接异常断开才会导致这种情况。

第二次(成功)

解决思路

  1. 在用户离开界面的时候(onHide)主动关闭ws连接,在重新进入时重连
  2. 不主动关闭ws连接,而是在发现连接断开要重连的时候,先主动断开连接,然后再重连。

第二种方法较好,因为onHide虽然能监听到小程序切换后台的行为并进行主动关闭ws连接的操作,但是不能保证可以覆盖所有可能导致ws异常关闭的场景,如果有某个场景没有主动关闭,那么整个重连机制将会失效,相反第二种方法只要监听到ws断开,就能确保重连成功,覆盖的场景更广。

第三次(优化)

问题

一段时间无通讯就会自动断开连接(原因未知,后端问题或者是微信限制),此时如果用户一直在界面里,就无法触发onShowonHide,此前的重连机制失效。

解决方法

在发送消息中进行断线重连,并且重连后调用接口更新消息历史记录。