小程序上视频列表的渲染与性能优化

2020-10-30 15:40:11 浏览数 (1)

| 导语  小程序的部分组件是由客户端渲染的原生组件,本文使用的 video 组件属于其中之一。视频列表涉及多个 video 组件的渲染、资源加载、滑动,处理不当会带来比较大的性能消耗。本文通过多种方案的对比,探讨视频列表渲染的最佳姿势,达到性能优化的目的。

一、背景

qq 小程序应用商店上的“值得一玩”模块,是由多个横向排列的视频组成的视频列表。该模块始终有一个视频完全处于可视区域,下一个视频的一部分露出。左右滑动列表切换下一个视频到可视区域,在 wifi 条件下自动播放可视区域视频。效果如下图所示:

根据业务需要,视频列表采用腾讯视频插件 txv-video 代替 video 组件,txv-video 底层也是 video 组件,只是在上面多封装了一层。

二.渲染方案对比

方案1:一次性渲染所有的 video 组件

刚拿到这个需求,想着 video 组件数量不多(最多5个),直接全部渲染影响应该不大。效果如下所示:

可以看到,模块加载时间过长,出现了 1-2s 的白屏现象。

下面从原生组件的渲染过程来解释原因。原生组件有非同层渲染、同层渲染2种渲染方式。

非同层渲染下,video 组件的渲染过程:

1. WebView 渲染一个占位元素,包括创建组件,计算组件位置、大小,通知客户端。

2. 客户端在相同的位置上,根据宽高插入一块原生区域进行渲染。

同层渲染下,video 组件的渲染过程(ios和安卓渲染方式不同,此处以安卓为例):

1. WebView 创建一个 embed DOM 节点并指定组件类型。

2. chromium 内核会创建一个  WebPlugin 实例,并生成一个渲染层 RenderLayer。

3. WebView 通知客户端创建原生组件。

4. 客户端将原生组件的画面绘制到步骤2创建的 RenderLayer 所绑定的 SurfaceTexture 上。通知 chromium 内核渲染该 RenderLayer 。

5. chromium 渲染该 embed 节点并上屏。

在非同层渲染下,原生组件的层级永远高于 Webview 的层级(无论 z-index 设置为多少),当组件位置发生改变时, Webview 通知客户端更新。这样会导致在切换视频时,video 组件位置的更新速度跟不上滑动速度,出现“连在一起”的现象。

安卓的同层渲染真正将原生组件视图加到了 WebView 的渲染流程中且 embed 节点是真正的 DOM 节点。当组件的位置发生改变时,WebView 更新,不用与客户端通信。目前 qq 小程序的 video 组件已经支持同层渲染。

可以看到,渲染过程涉及 WebView、客户端、内核的一系列操作。因此,当 video 组件个数越多时,渲染这些 video 组件耗费的时间越长。

方案2:开始只加载1个 video 组件,移动到目标区域后再加载 video 组件

根据方案1的结论,减少模块加载时渲染的video 组件个数。开始只渲染1个 video 组件,其余不在可视区域的用 image 标签代替,移动到可视区域后再渲染 video 组件。效果如下所示:

可以看到,相比方案1,模块加载时间明显减少。

同时发现:在 wifi 条件下自动播放可视区域视频,左右滑动时会发生卡顿现象。如下所示:

尝试了开启 3d 加速、先暂停视频再滑动(避免直接滑动视频带来的性能问题)等方法都没有明显的改进。在非 wifi 情况下,不自动播放可视区域视频,不会发生卡顿现象。

滑动切换播放视频的过程如下图所示:

视图层 Webview 处理 touch 事件,调用 callMethod 与 逻辑层 Appservice 通信;Appservice 收到当前 video 组件的 index 信息后,setVid 加载资源,执行 play 操作, 通知客户端;客户端播放当前视频,暂停上一个视频。

从表象上看,卡顿现象的发生与滑动到目标区域后是否播放视频有关。是 Appservice 与客户端的通信阻塞了 Webview 的操作?还是播放视频导致了卡顿的发生呢?

小程序的卡顿通常发生在逻辑层与视图层频繁地通信、页面节点数过多等情况下,Appservice 与客户端的简单一次通信并不会造成卡顿的发生,猜想是播放视频导致了卡顿。去除自动播放视频的操作,手动控制 video 组件播放或暂停,切换视频时发现卡顿依然明显。卡顿与滑动到目标区域后是否立即播放视频没有关系,而与播放过的video组件个数有关,播放过的video组件个数越多,切换时越卡顿。

下面从 chromium 内核对 <video> 标签的处理来解释原因。

chromium 是通过 WebKit 解析网页内容的。当 WebKit 遇到 <video> 标签时,就会创建一个播放器实例。WebKit 并没有自己实现播放器,而仅仅是创建一个播放器接口。通过这个播放器接口,可以使用平台提供的播放器来播放视频的内容。当创建  <video> 标签时,仅仅创建了类型为 HTMLMediaElement 的 DOM 节点。当为 video 组件的 src 赋值时,会调用接口创建播放器,进行视频资源信息加载、视频解码等一系列操作,“真正”渲染 video 组件。

上述操作会占用一部分系统资源,播放过的 video 组件个数越多,占用的系统资源越多,切换视频时越卡顿。即使暂停视频也没用,video组件实例仍然存在没被销毁,依旧占用系统资源。

方案3:video 组件实例复用

根据方案1、2的结论,减少 video 组件实例个数。考虑采用3个 video 组件,索引为 index % 3 的 video 组件用来播放当前视频,索引为 ( index 1 ) % 3 的 video 组件用来预加载下一个视频,索引为( index 2 ) % 3 的 video 组件用来缓存上一个视频。在左右滑动切换时仅更改这3个 video 组件的 transform,达到视觉隐藏和实例复用的目的。从需求背景可以看到,本需求要求下一个视频的一部分露出,与本方案不太符合,本方案更适合一个视频占满整个可视区域的使用场景,比如微视无限列表。

方案4:video 组件即用即毁,其余用 image 标签代替

从上面的分析可以得到,减少模块加载时间和提高视频切换性能的关键在于减少 video 组件实例数,需要及时销毁当前没有使用的 video 组件实例。因此,只渲染可视区域的 video 组件,其余用 image 标签代替,当 video 组件滑出可视区域后,及时销毁该 video 组件实例。

关键代码如下所示:

 <txv-video

代码语言:javascript复制
            class="topic-video"
            wx:if="{{ videoVid[index] && viewportList[index] }}"
            vid="{{ videoVid[index] }}"
            playerid="topic-video-{{ index }}"
            poster="{{ item.videoCover }}"
            bindplay="playVideo"
            bindpause="pauseVideo"
            muted="{{ true }}"
            showMuteBtn="{{ true }}"
            enableProgressGesture="{{ false }}"
            data-app="{{ item }}"
            bindtap="tapVideo"
            autoplay="{{ videoVid[index] }}"
            showFullscreenBtn="{{ false }}"
            showCenterPlayBtn="{{ false }}"
          ></txv-video>
          <view wx:if="{{ !videoVid[index] || !viewportList[index] }}"
代码语言:javascript复制
class="topic-video-image">
            <image src="{{ item.videoCover }}" class="topic-video-cover">
代码语言:javascript复制
</image>
            <image
              src="../../images/ad-friend-watch/topic-video-play-btn.png"
              class="topic-video-play-btn"
              bind:tap="tapVideo"
            ></image>
          </view>

视频切换效果如下所示:

可以看到,切换视频时不存在卡顿现象,性能得到了明显的提升。

本方案对 video 组件即用即毁,滑动到可视区域时才渲染组件,相比 video 组件实例复用,花费的时间会不会多很多呢?

从方案2中的分析可以得到,在 video 组件的 src 赋值前,仅创建了一个 DOM 节点,该步骤的时间花销较小。在 video 组件的 src 赋值后,才“真正”渲染 video 组件。所以,对于切换到 src 没有被赋值的 video 组件,本方案和 video 组件实例复用的时间花销差不多。但是,对于视频被播放过再切回该视频的情况,因为该 video 组件已经被销毁,会再次经历渲染 video 组件、加载资源等操作,有一定的时间损耗和用户流量的损耗。考虑到非wifi情况下不会自动播放视频,视频时长较短(15-30秒),且用户来回滑的行为概率较小,相比明显卡顿甚至卡死现象,还是更能让人接受。

数据分析方法入门

从0开始打造UI框架:动态化框架Scrollview物理学算法解析

直播插件体系设计

喜欢本文?快点“在看”支持一下↓↓

0 人点赞