视频无缝切换(ios黑一下问题也解决了),预加载图片资源等小程序二三点优化

3,350 阅读6分钟

缘由

没啥别的理由,就是闲的。最近又开始写小程序了,不过心态不太一样了,因为其实大家都是js。你不能总是觉得小程序low,其实人家的思维逻辑都是互通的。

今天把一些性能优化的解决方案拿出来分享下。能比较有效的解决页面的一些卡顿和存在的一些问题

提示:博主用uniapp框架,但是原理都是一样的,一些uniapp的api,你把uni替换成wx就OK了

一、data数据的性能优化,基本操作

这个其实和vue一个道理,所以大家不要把不参与渲染的数据定义在data里面了,不管是用了框架的还是原生小程序框架的。最好是大家把未定义渲染的数据放在created开始,进行约定规范,能大幅降低渲染成本。

再一个,如果你的数据不进行渲染操作,那么比如for循环赋值之类的操作,最好是先存储一下变量。最后再赋值回去。能减少无意间的性能开销,这个主要是针对vue,react框架这类框架。

举例:

this.list.map()

改为

const list = this.list

list.map()

this.list = list

这样不只是性能优化,你的代码整洁度也能大幅上升

二、路由优化,能大幅降低性能问题

首先微信小程序为了性能优化,对页面进行了10层最大缓存的处理,并且当10层缓存满了之后,navigateTo函数会直接报错,无法使用。除了我们一般经常需要注意页面层级问题外,其实还有别的方式来解决这个问题。当然一般的电商购物类的页面没有这么苛刻。但是这次公司的app是视频播放类app,本身视频播放就很吃性能,而且看似页面就那么几个,但是极度容易出现层级满了,然后页面跳转失效问题。

如果用reLaunch会释放资源,那么缓存失效,更卡

redirectTo呢依旧无法避免层级超过10层问题

所以就有了下面这个函数

/*
{
 url: 'pages/index',
 type: 'navigate' //redirect分别对应navigateTo和redirectTo
}
*/
const pageJump = ({
 url,
 type = 'navigate'
}) => {
 const pageStack = getCurrentPages()
 const pageLen = pageStack.length
 const home = pageLen + 1 //如果需要返回首页
 // 获取页面所在栈位置
 const page = u => {
    //获取指定页面所在数
    for (let i = 0; i < pageStack.length; i++) {
   if (pageStack[i].route === u) {
    return i
   }
    }
 }
 const pa1 = page(pageStack[pageLen - 1].route) // 当前页数
 const pa2 = page(url) // 需要跳转的页
 //实际需要跳转的页数
 const pageNum = pa1 - pa2
 if (pageNum >= 0) {
    uni.navigateBack({
   delta: pageNum
    })
    return true
 }
 if (type === 'navigate') {
    uni.navigateTo({
   url: '/' + url
    })
    return true
 }
 if (type === 'redirect') {
    uni.redirectTo({
   url: '/' + url
    })
    return true
 }
}

使用很简单,你直接传入对应的下一个页面的地址就行了,注意不要有前面的‘/’

这样页面在跳转的时候就会优先找历史中缓存的页面,没有的情况下才会新开页面。有效避免了10层最大缓存问题,并且现在流畅性提高一个档次(因为会复用缓存)。

三、小程序模拟multipart/form-data数据提交和post大数据引发的问题

这个不是性能问题,属于个人对于小程序框架的bug踩坑

1、首先小程序一般是不支持formdata方式的,我们改造之后变成可以做到

header部分

header: {
 'content-type': 'multipart/form-data; boundary=XXX'
},

data传参部分用这个函数处理一下

// 修改为formdata
getformdata(obj = {}) {
 let result = ''
 for (let name of Object.keys(obj)) {
  let value = obj[name];
  result +=
   '\r\n--XXX' +
   '\r\nContent-Disposition: form-data; name=\"' + name + '\"' +
   '\r\n' +
   '\r\n' + value
 }
 return result + '\r\n--XXX--'
}

这个当初要这样做是因为旷视的人脸识别接口需要base64数据并且需要是formdata方式进行上传

2、引发的小程序真机调试的bug

当post里面的data数据超过100kb会直接无法请求接口数据,导致接口函数直接没有任何执行效果。万分注意该问题

四、图片压缩问题

除了小程序官方提供的图片压缩函数wx.compressImage,我们还可以用canvas进行图片压缩,同时我比较不推荐官方的wx.compressImage,因为这个api会导致相册新增图片。像我之前的人脸识别频繁拍照的,那真是灾难。多出来几千张照片

函数呢写好了,

async compress({ w, h, canvaId, quality = 0.1, src = '' }) {
   const ctx = uni.createCanvasContext('compress')
   const [, img] = await uni.getImageInfo({ src })
   const { path, width, height } = img
   ctx.drawImage(path, 0, 0, width, height, 0, 0, w, h) //描述图片到画布上
   return new Promise((reslove, reject) => {
    ctx.draw(false, async () => {
     const [err, nimg] = await uni.canvasToTempFilePath({
        fileType: 'jpg',
        canvasId: 'compress',
        quality: quality
     })
     if (err) {
        reject(err)
        return false
     } else {
        reslove(nimg.tempFilePath)
     }
    })
   })
    }

使用

const img = await this.compress({
   w: windowWidth, // 希望的图片宽高
   h: windowHeight,
   canvaId: 'compress',
   quality: 0.5, // 压缩质量
   src:
    'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1602222973914&di=80f148ded356938570ed00fe9f68b816&imgtype=0&src=http%3A%2F%2Fwww.deskcar.com%2Fdesktop%2Ffengjing%2F2007125102851%2F10.jpg'
    })

注意html部分

这里我提供的函数没有对canvas的图片的宽高动态变化,这个大家注意下。在getImageInfo这个接口之后注意要给canvas赋值下图片本身的宽高。避免矿高比变形。

五、图片预加载

这个其实就比较常用,特别是我们这边有几个比较大的前置图片,如果进入页面再加载那么背景图就无法里面加载好。

这里用 uni.getImageInfo({ src: ‘’ })就可以了,注意看下官方的api,是在success之后会得到一个本地的临时图片地址,我们拿这个地址去全局使用就行了

官方文档地址:developers.weixin.qq.com/miniprogram…

六、ios和安卓多个视频交替切换,会黑一下的问题

这个原因是因为视频播放之前需要进行解码,而解码是需要时间的,那么在这个之前,播放肯定是黑的。安卓还好,ios非常严重。产品大大说这个必须要改

1、我们这个是多个视频切换,那么我们增加背景视频或者背景图片避免黑屏尴尬

2、但是这样我们如果只是监听onplay是不行的,还是会比较明显的黑一下

并且这里引出一个bug,当多个视频之间相互切换的时候,上一个视频帧数据会影响下一个视频帧数据。如果是单纯的去监听api也是没有用的。

最后我的代码是这样的。做到了无缝切换的感觉

<template>
 <view>
    <video
   class="vie"
   :show-fullscreen-btn="false"
   :show-play-btn="false"
   :show-center-play-btn="false"
   :enable-progress-gesture="false"
   loop
   :controls="false"
   muted
   :autoplay="true"
   :style="{ 'z-index': isplay ? 1 : 100 }"
   src="https://dtman.guiji.ai/nfs/ss-file/xz/v1/video/background_2.mp4"
    ></video>
    <video
   class="vie"
   :show-fullscreen-btn="false"
   :show-play-btn="false"
   :show-center-play-btn="false"
   :enable-progress-gesture="false"
   :style="{ 'z-index': isplay ? 100 : 1 }"
   :controls="false"
   @timeupdate="ontimeupdate"
   @ended="onended"
   :src="show"
   :initial-time='0.01'
   :autoplay="true"
    ></video>
    <button style="top: 300px;" @click="next">下一个视频</button>
 </view>
</template>
<script>
export default {
 data() {
    return {
   isplay: false, // 是否播放了
   show: '', // 播放的视频地址
    }
 },
 created() {
    // 这里定义的变量不会参与渲染
    this.check = false // 当前是否切换地址了
    this.index = 0 
    this.src = [
    ‘公司的视频,自己换’,
                ‘公司的视频,自己换’,
                ‘公司的视频,自己换’,
                ‘公司的视频,自己换’,
     ]
   },
   methods: {
    next() {
     this.isplay = false
     this.show = ''

     if (this.index === this.src.length - 1) {
        this.index = 0
     }
     // 模拟接口请求,500毫秒延迟
     setTimeout(() => {
        this.show = this.src[this.index] //+ '?t=' +  new Date().getTime()
        this.index = this.index + 1
        this.check = true
     }, 500)
    },
    onended() {
     this.isplay = false
    },
    //监听视频播放时间更新
    ontimeupdate(e) {
     const currentTime = e.detail.currentTime
     // 排除干扰因素
     if (currentTime <= 0) return false

     // 只有当前未播放,切换视频地址情况下,并且时间开始为零点几才可以撤销遮罩视频
     // 原理:多个视频切换播放的时候上一个视频播放帧会因为卡顿等问题延迟到下一个视频播放
     if (!this.isplay && this.check && currentTime.toString().substring(0, 1) === '0') {
        this.isplay = true
        this.check = false
     }
    }
   }
  }
  </script>

代码为uniapp的代码,但是我已经大幅简化了,拿去试一试吧

摸鱼结束

吐槽下掘金的文章编辑器感觉越来越难用。代码块赋值黏贴没格式,然后放过来之后呢文章的格式全部不对。搞得我需要先放语雀里面重新编辑下在贴回来