微信小程序新渲染引擎Skyline介绍

官方文档: Skyline 渲染引擎 / 概览 / 介绍 (qq.com)

〇. 概述

小程序一直以来采用的都是 AppServiceWebView 的双线程模型,基于 WebView 和原生控件混合渲染的方式,小程序优化扩展了 Web 的基础能力,保证了在移动端上有良好的性能和用户体验。Web 技术至今已有 30 多年历史,作为一款强大的渲染引擎,它有着良好的兼容性和丰富的特性。 尽管各大厂商在不断优化 Web 性能,但由于其繁重的历史包袱和复杂的渲染流程,使得 Web 在移动端的表现与原生应用仍有一定差距。

为了进一步优化小程序性能,提供更为接近原生的用户体验,微信在 WebView 渲染之外新增了一个渲染引擎Skyline, Skyline 在微信小程序基础库3.0.0发布时 (2023年7月5日) 正式启用。它采用了新的架构设计,优化了小程序扩展Web的基础能力,保证了在移动端上有良好的性能和用户体验。Skyline,其使用更精简高效的渲染管线,并带来诸多增强特性,让 Skyline 拥有更接近原生渲染的性能体验。

官方Skyline体验demo:

一. 新架构

Webview 下的双线程模型

官方介绍: 小程序宿主环境 | 微信开放文档 (qq.com)

小程序运行环境分成渲染层逻辑层 ,其中WXML模板和WXSS样式工作在渲染层,JS脚本工作在逻辑层。

小程序的渲染层和逻辑层分别由2个线程管理:渲染层的界面使用了WebView进行渲染;逻辑层采用JsCore线程运行JS脚本。一个小程序存在多个界面,所以渲染层存在多个WebView线程,这两个线程的通信会经由微信客户端(下文中也会采用Native来代指微信客户端)做中转,逻辑层发送网络请求也经由Native转发,小程序的通信模型下图所示。

问题:

  1. WebView因为历史原因存在渲染管线臃肿的问题,导致在移动端的表现与原生应用仍然有差距。
  2. 每个页面都需要实例化一个JS引擎(WebView),导致内存占用多,影响应用性能,因此小程序对打开的页面数量有限制(页面栈最多10个)。
  3. 渲染层和逻辑层使用JSBridge通信,在复杂场景下(比如拖动元素的交互动画)会存在性能问题,不过可以通过WXS来解决这个问题,但直接去除JSBridge会更好。

Skyline 新架构

当小程序基于 WebView 环境下时,WebView 的 JS 逻辑、DOM 树创建、CSS 解析、样式计算、Layout、Paint (Composite) 都发生在同一线程,在 WebView 上执行过多的 JS 逻辑可能阻塞渲染,导致界面卡顿。以此为前提,小程序同时考虑了性能与安全,采用了目前称为「双线程模型」的架构。

Skyline 环境下,我们尝试改变这一情况:Skyline 创建了一条渲染线程来负责 Layout, Composite 和 Paint 等渲染任务,并在 AppService 中划出一个独立的上下文,来运行之前 WebView 承担的 JS 逻辑、DOM 树创建等逻辑。这种新的架构相比原有的 WebView 架构,有以下特点:

  • 界面更不容易被逻辑阻塞,进一步减少卡顿
  • 无需为每个页面新建一个 JS 引擎实例(WebView),减少了内存、时间开销
  • 框架可以在页面之间共享更多的资源,进一步减少运行时内存、时间开销
  • 框架的代码之间无需再通过 JSBridge 进行数据交换,减少了大量通信时间开销

而与此同时,这个新的架构能很好地保持和原有架构的兼容性,基于 WebView 环境的小程序代码基本上无需任何改动即可直接在新的架构下运行。WXS 由于被移到 AppService 中,虽然逻辑本身无需改动,但询问页面信息等接口会变为异步,效率也可能有所下降;为此,微信同时推出了新的 Worklet 机制,它比原有的 WXS 更靠近渲染流程,用以高性能地构建各种复杂的动画效果。

新的渲染流程如下图所示:

ps: skyline 使用的可能是 flutter 绘制方案, 见: www.zhihu.com/question/54...

Skyline 支持与 WebView 混合使用

小程序支持页面使用 WebView 或 Skyline 任一模式进行渲染,Skyline 页面可以和 WebView 页面混跳,开发者可以页面粒度按需适配 Skyline。

js 复制代码
// page.json
// skyline 渲染
{
    "renderer": "skyline"
}
​
// webview 渲染
{
    "renderer": "webview"
}

二. 新特性

worklet 动画函数

文档: Skyline 渲染引擎 / 增强特性 / Worklet 动画 (qq.com)

在双线程模式里,为了解决交互动画(如拖动元素)时线程间通信的性能问题,引入了 WXS,让部分 JS 逻辑放到 WebView 里执行,以此解决通信时的性能问题,但在 skyline 里,WXS 被移动到了 AppService 中,导致效率会有所下降,所以推出 Worklet 进行替代

使用 worklet 动画能力时确保以下两项:

  • 确保开发者工具右上角 > 详情 > 本地设置里的 将 JS 编译成 ES5 选项被勾选上 (代码包体积会少量增加)
  • worklet 动画相关接口仅在 Skyline 渲染模式下才能使用

概念一:worklet函数

worklet 函数是一种声明在开发者代码中,可运行在 JS 线程或 UI 线程的函数,函数体顶部有 'worklet' 指令声明, 而非 worklet 函数只能运行在 JS 线程中。

js 复制代码
function someWorklet(greeting) {
  'worklet';
  console.log(greeting);
}
// 运行在 JS 线程
someWorklet('hello') // print: hello
​
// 运行在 UI 线程
wx.worklet.runOnUI(someWorklet)('hello') // print: [ui] hello

worklet 函数间相互调用

js 复制代码
const name = 'skyline'
​
function anotherWorklet() {
  'worklet';
  return 'hello ' + name;
}
​
// worklet 函数间可互相调用
function someWorklet() {
  'worklet';
  const greeting = anotherWorklet();
  console.log('another worklet says ', greeting);
}
​
wx.worklet.runOnUI(someWorklet)() // print: [ui] another worklet says hello skyline

从 UI 线程调回到 JS 线程

js 复制代码
function someFunc(greeting) {
  console.log('hello', greeting);
}
function someWorklet() {
  'worklet';
  // 访问非 worklet 函数时,需使用 runOnJS
  // someFunc 运行在 JS 线程
  runOnJS(someFunc)('skyline');
}
​
wx.worklet.runOnUI(someWorklet)(); // print: hello skyline

概念二:共享变量

共享变量是指在 JS 线程创建,可在两个线程间同步的变量。

js 复制代码
const { shared, runOnUI } = wx.worklet
​
const offset = shared(0)
function someWorklet() {
  'worklet'
  console.log(offset.value) // print: 1
  // 在 UI 线程修改
  offset.value = 2
  console.log(offset.value) // print: 2
}
  // 在 JS 线程修改
offset.value = 1
runOnUI(someWorklet)()

shared 函数创建的变量,我们称为 sharedValue 共享变量。用法上可类比 vue3 中的 ref,对它的读写都需要通过 .value 属性,但需注意的是它们并不是一个概念。sharedValue 的用途主要如下。

跨线程共享数据

worklet 函数捕获的外部变量,实际上会被序列化后生成在 UI 线程的拷贝,如下代码中, someWorklet 捕获了 obj 变量,尽管我们修改了 objname 属性,但在 someWorklet 声明的位置,obj 已经被序列化发送到了 UI 线程,因此后续的修改是无法同步的。

js 复制代码
const obj = { name: 'skyline'}
function someWorklet() {
  'worklet'
  console.log(obj.name) // 输出的仍旧是 skyline
}
obj.name = 'change name'
​
wx.worklet.runOnUI(someWorklet)() 

sharedValue 就是用来在线程间同步状态变化的变量。

js 复制代码
const { shared, runOnUI } = wx.worklet
​
const offset = shared(0)
function someWorklet() {
  'worklet'
  console.log(offset.value) // 输出的是新值 1
}
offset.value = 1
runOnUI(someWorklet)() 

驱动动画

worklet 函数和共享变量就是用来解决交互动画问题的。相关接口 applyAnimatedStyle 可通过页面/组件实例访问,接口文档参考

js 复制代码
<view id="moved-box"></view>
<view id="btn" bind:tap="tap">点击驱动小球移动</view>
Page({
  onLoad() {
    const offset = wx.worklet.shared(0)
    this.applyAnimatedStyle('#moved-box', () => {
      'worklet';
      return {
        transform: `translateX(${offset.value}px)`
      }
    })
    this._offset = offset
  },
  tap() {
    // 点击时修改 sharedValue 值,驱动小球移动
    this._offset.value = Math.random()
  }
})

当点击按钮 #btn 时,我们用随机数给 offset 进行赋值,小球会随之移动。

applyAnimatedStyle 接口的第二个参数 updater 为一个 worklet 函数,其捕获了共享变量 offset,当 offset 的值变化时,updater 会重新执行,并将返回的新 styleObject 应用到选中节点上。

当然,光看这个例子,跟用 setData 看好像没有什么区别。但当 worklet 动画和手势结合时,就产生了质变。

手势处理

js 复制代码
<pan-gesture-handler onGestureEvent="handlepan">
  <view class="circle"></view>
</pan-gesture-handler>
Page({
  onLoad() {
    const offset = wx.worklet.shared(0);
    this.applyAnimatedStyle('.circle', () => {
      'worklet';
      return {
        transform: `translateX${offset.value}px`
      };
    });
    this._offset = offset;
  },
  handlepan(evt) {
    'worklet';
    if (evt.state === GestureState.ACTIVE) {
      this._offset.value += evt.deltaX;
    }
  }
});

当手指在 circle 节点上移动时,会产生平滑的拖动效果。handlepan 回调触发在 UI 线程,同时我们修改了 offset 的值,会在 UI 线程产生动画,不必再绕回到 JS 线程。

手势系统

业务开发中,我们常需要监听节点 touch 事件,处理拖拽、缩放相关逻辑。由于 Skyline 采用双线程架构,在进行这样的交互动画时,会具有较大的异步延迟,这点可以参考 wxs 响应事件

Skylinewxs 代码运行在 JS 线程,而事件产生在 UI 线程,因此 wxs 动画 性能有所降低,为了提升小程序交互体验的效果,我们内置了一批手势组件,使用手势组件的优势包括

  1. 免去开发者监听 touch 事件,自行计算手势逻辑的复杂步骤
  2. 手势组件直接在 UI 线程响应,避免了传递到 JS 线程带来的延迟

效果演示:

自定义路由

小程序采用多 WebView 架构,页面间跳转形式十分单一,仅能从右到左进行动画。而原生 App 的动画形式则多种多样,如从底部弹起,页面下沉,半屏等。

Skyline 渲染引擎下,页面有两种渲染模式: WebViewSkyline,它们通过页面配置中的 renderer 字段进行区分。在连续的 Skyline 页面间跳转时,可实现自定义路由效果。

为降低开发成本,基础库预设了一批常见的路由动画效果, 见: Skyline 渲染引擎 / 增强特性 / 预设路由效果 (qq.com)

仅需在路由跳转时,指定对应的 routeType

js 复制代码
wx.navigateTo({
  url: 'xxx',
  routeType: 'wx://modal'
})

共享元素动画

原生 App 中我们常见到这样的交互,如从商品列表页进入详情页过程中,商品图片在页面间飞跃,使得过渡效果更加平滑,另一个案例是朋友圈的图片预览放大功能。在 Skyline 渲染模式下,我们称其为共享元素动画,可通过 share-element 组件来实现。

在连续的 Skyline 页跳转时,页面间 key 相同的 share-element 节点将产生飞跃特效,开发者可自定义插值方式和动画曲线。通常作用于图片,为保证动画效果,前后页面的 share-element 子节点结构应该尽量保持一致。

内置组件更新

  1. 提供 grid-view 瀑布流组件。

瀑布流是一种常用的列表布局方式,得益于 Skyline 在布局过程中的可控性,我们直接在底层实现并提供出来,渲染性能要比 WebView 更优。

  1. 提供 snapshot 截图组件。

大多数小程序都会基于 canvas 实现自定义分享图的功能,一方面,需要通过 canvas 绘图指令手动实现,较为繁琐;另一方面,在分享图的布局较复杂时,或者在制作长图时会受限于系统对 canvas 尺寸限制,canvas 的方案实现成本都会很大。得益于 Skyline 在渲染过程中的可控性,Skyline 能直接对 WXML 子树进行截图,因此我们直接提供了截图组件,这样能复用更完善的 WXSS 能力,极大降低开发成本。

  1. scroll-view 组件支持列表反转。

在聊天对话的场景下,列表的滚动常常是反向的(往底部往上滚动),若使用正向滚动来模拟会有很多多余的逻辑,而且容易出现跳动,而 scroll-view 提供的 reverse 属性很好的解决这一问题。

三. 兼容性及最佳实践

几个比较大的兼容性问题

  • 不支持原生导航栏, 需自行实现自定义导航栏
  • 不支持页面全局滚动, 在需要滚动的区域使用 scroll-view 实现
  • 暂不支持fixed定位, em单位,
  • 不支持通配选择器, 属性选择器, 兄弟选择器等
  • 默认值与webview差异较大, 如默认flex布局且flex-flow: column;, 默认border-box盒模型等

兼容建议: Skyline 渲染引擎 / 从 WebView 迁移 / 常见兼容问题 (qq.com)

最佳实践建议: Skyline 渲染引擎 / 从 WebView 迁移 / 最佳实践 (qq.com)

四. 总结

优点

  • 接近原生的交互体验 (手势系统, 自定义路由, 共享元素)
  • 节省内存, 性能提升 (wxss预编译, 底层为flutter)
  • 组件更新带来的新功能 (如截图组件)

缺点

  • 新技术稳定性差, 目前不太稳定
  • css很多属性在styline的wxss下不支持, 开发心智负担大
  • 新的worklet函数, 自定义路由等语法可读性差, 学习成本高
相关推荐
丁总学Java16 小时前
微信小程序,点击bindtap事件后,没有跳转到详情页,有可能是app.json中没有正确配置页面路径
微信小程序·小程序·json
mosen86817 小时前
Uniapp去除顶部导航栏-小程序、H5、APP适用
vue.js·微信小程序·小程序·uni-app·uniapp
qq229511650218 小时前
微信小程序的汽车维修预约管理系统
微信小程序·小程序·汽车
小飞哥liac1 天前
微信小程序的组件
微信小程序
stormjun1 天前
Java基于微信小程序的私家车位共享系统(附源码,文档)
java·微信小程序·共享停车位·私家车共享停车位小程序·停车位共享
Bessie2341 天前
微信小程序eval无法使用的替代方案
微信小程序·小程序·uni-app
shenweihong1 天前
javascript实现md5算法(支持微信小程序),可分多次计算
javascript·算法·微信小程序
2 天前
微信小程序运营日记(第四天)
微信小程序·小程序
qq22951165022 天前
小程序Android系统 校园二手物品交换平台APP
微信小程序·uni-app
一只不会编程的猫2 天前
微信小程序配置
微信小程序·小程序