目录

微信小程序运行机制解析

微信小程序运行机制解析

在微信小程序诞生之前,最流行的技术应该是Hybrid 混合开发。 Hybrid 混合开发有两个优势,一是跨平台,二是热更新。而微信小程序就更像是运行在微信这个特定环境下的 Hybrid 技术。

接下来从四个方面聊一聊小程序的运行机制,看一看,小程序相比Hybrid又有哪些创新点。

相信做过微信小程序开发的小伙伴都知道,微信有一个自己开发的语言 wxs 。那我们也不妨思考一下这么一个问题,既然 JavaScript 是小程序的主要开发语言,为什么微信小程序还要自己在创造一个语言呢?

一、小程序的启动机制

小程序启动会有两种情况

  • 冷启动:是指用户首次打开,或者是小程序被微信主动销毁后,再次打开,这时候小程序是需要重新加载启动
  • 热启动:假如用户已经打开过某个小程序,过了一段时间以后,用户再次去打开这个小程序,则小程序无需要再次重新启动小程序,只需要把后台运行的小程序切换到前台

这时候你可能会发现这么一个问题:小程序从本地读取缓存,那小程序什么时候可有应用新的版本呢?

小程序在冷启动时,会检测是否有新版本,如果发现有新的版本,就会异步下载新版本的代码包,并且同时用客户端本地的代码进行启动,也就是新版本的小程序,需要等到下一次冷启动时才会用上。如果你需要马上用上新的版本,则可以使用 wx.getUpdateManager 这个接口进行更新处理

那这时候你可能还有一个问题:微信什么时候主动销毁我们的小程序呢?

这里有两种情况,微信会主动销毁我们的小程序:

  1. 小程序在后台运行超过一定的时间,目前大概是五分钟

  2. 当短时间内,目前是5s 连续上次以上收到系统内存的警告(运行内存不足,请重新打开该小程序 )

    当然了,这种情况对用户体验是非常不友好的,必要的时候,我们也可以使用 wx.onMemoryWarning 接口监听内存的警告事件,提请做一些处理

二、小程序的两种状态

  • 前台状态:小程序启动以后,界面被展示给用户,此时小程序处于前台状态

  • 后台状态:当用户点击右上角按钮关闭小程序,或者离开微信时,小程序并不会立刻终止运行,小程序还会再运行一段事件,这时处于后台状态。

    这种状态有点类似于浏览器的tab页,没有关闭,只是被切换了

三、小程序的双线程架构

为了安全和管控,小程序使用双线程执行:视图线程和逻辑线程

  • View 视图线程:主要提供各类组建、渲染界面
  • App Service 逻辑线程:提供 API 处理业务逻辑

两个线程都是通过底层的 WeiXinJSBridge 进行通讯

接下来我们可以通过下面几个问题来理解一下小程序的运行原理

  1. 小程序怎么实现视图更新的?

    简单来说,它是通过 setData 来实现的

    在Hybrid 应用中,是通过一个叫 evaluateJavaScript() 的方法来执行js的,在这个方法还可以通过回调获得js 方法的返回值

    webView.evaluateJavaScript("javascript: main()", 
      new ValueCallback<string>() {
        @override
        public void onReceiveValue(String value) {
          ...
        }
      }
    )

    在程序中,视图层和逻辑层的数据传输,实际上都是通过底层的 WeixinJSBridge ,通过原生 evaluateJavaScript 实现的。

    setData 要求更新的数据首先会将这个数据转化成字符串,接着将这个字符串于代码拼接成JavaScript 脚本,最后,把拼接的内容传给 evaluateJavaScript() 去执行。

    当然小程序在视图更新上也是有做 虚拟 DOM 的优化的。所以从数据到达视图层的更新,并不是实时进行的

  2. setData 可能遇到的“坑”?

    从前面我们可以看到,视图线程和逻辑线程是分开的,两个线程之间通过前置的 setData 方法来驱动数据交换,还要通过 WeixinJSBridge 进行中转,这个中转的效率是非常低的。所以有时候安卓用户在进行界面滑动时,可能会感觉到页面卡顿,这是因为视图线程一直在努力进行渲染,逻辑层发来的更新请求被阻塞了,当这种阻塞达到200ms 以上时,视图渲染便会卡顿,卡顿不仅仅和更新的频率相关,和更新的数据量也有关系,当setData更新大列表数据时,或者更新一个size很大的图片时,也容易造成卡顿。为什么只说安卓呢?安卓用户表示IOS就不会出现这种情况吗?在iOS中,小程序的页面是由多个 WKWebView 组成的,在系统内存紧张时,一部分 WKWebView 会被系统内存回收掉,也就是说之前打开过的小程序界面,会退出历史记录,这些页面我们是无法回收的

四、微信是怎么实现视图线程与逻辑线程的?

与小程序的视图线程相关的编译器有两个,

  • wcc 编译器: wccWXML 的编译器,主要功能是将 WXML 文件编译成 JavaScript 代码

  • wcsc 编译器: wcscWXSS 编译器,主要是负责将 WXSS 文件编译成JavaScript代码

    小程序的视图层是在 Polymer 框架的基础之上,基于 WebComponent 标准实现的。小程序的视图渲染有点类似与 VueDOM 渲染,都是通过 虚拟DOM 来实现的。不同的是,小程序除了一般视图组件,还有原生组件,小程序将一般视图组件放在下面,将解析后的原生组件放在上面

    小程序逻辑线程的实现,我们可以从它的生命周期来体会,

    小程序的主要生命周期有以下五个:

    onLoadonShowonReadyonHideonUnload

    当一个小程序页面启动时,首先触发的是 onLoadonShow 生命周期函数,页面初始化装载完毕之后, Notify 通知 逻辑线程逻辑线程 将初始化的Data数据发给 视图线程 ,由 视图线程 渲染,完成首次渲染以后, 视图线程 通知 逻辑线程 渲染完成,派发 onReady 事件,监听到这个事件,代表着也没你可以进行交互了。

    此时,视图进入持续渲染状态,在运行状态下,用户触发了更新事件,如输入等操作,会触发逻辑线程的事件函数,可能触发了 setData() ,又向 视图线程 发送更新数据, 视图线程 再次执行更新,当用户跳转到其他小程序或者离开微信时,小程序进入后台状态,此时逻辑线程派发 onHide 事件。而当后台切入前台时,又会从 onShow 事件开始走,如果页面页面被销毁,则会派回 onUnload 事件

微信为什么要打造一个WXS语言?

WXSweinxinScript 的缩写,它结合 WXML 可以构造出页面的组件结构, WXS 不依赖于运行时的基础库版本,可以在所有版本的微信小程序里运行, WXSJavaSript 是不同的语言。虽然 WXS 也是逻辑代码,但是它不是运行在 逻辑线程 里的,它是运行在 视图线程 里的,通过直接操作视图数据,避免了跨线程的通讯开销。(这时聪明的你就已经发现了,高贵的苹果用户,运行 WXS 的速度就要比安卓的快一些了。)微信打造 WXS 主要是小程序的双线程架构在数据更新上有性能瓶颈,所有才打造了 WXS ,虽然 WXS 可以提交视图数据的更新效率,但是,他也不是没有缺陷的,它存在以下这些问题:

  • WXS 运行环境和其他JS代码是隔离的,因此,WXS是不可以调用JavaScript 代码里定义的函数的,也是不可以调用小程序提供的wx 开头的 API接口
  • WXS 函数不能作为视图模板中的事件回调句柄。
  • 由于设备的差异,WXS 在iOS设备上运行速度要比JS快,大约是2-20倍,但是在安卓设备上,两者几乎没有差异

总结

小程序采用的是双线程架构,一个线程负责视图的渲染,另一个负责业务逻辑的处理。连个线程都是通过 WeixinJSBridge 与微信Native 底层进行通讯。两个线程之间进行的事件与数据的交互,也是通过 WeixinJSBridge 来完成的。所有的平台能力和硬件能力,也是通过 WeixinJSBridge 间接提供的。由于 setData 在频繁更新数据和大数据更新上存在性能瓶颈,影响渲染效率,所以微信就引入了 WXS 变成语言。一般开发时我们可以这样处理,一开始,从后端接口接收回来的初始化数据,在页面 onLoad 之前,就存放到 Data 数据中,用于视图的初始化渲染,后续的是视图交互与更新,如果不与后台有关,我们就使用 WXS 直接在视图中进行完成,这样可以提高渲染性能。