架构篇-通讯系统设计|小程序原理专题
# 前言
渲染线程讲解完后。这一章节我们来探索一下微信小程序的通讯系统。来看一下渲染线程是如何通讯逻辑线程的。
本章节内容分解:
- 小程序通讯系统原理
# 通讯系统
先重顾看一下微信小程序的线程架构图。

可以看到sendData并不是直接指向逻辑层的(也就是Event这两条线)。小程序逻辑层和渲染层的通信会由 Native (微信客户端)做中转,逻辑层发送网络请求也经由 Native 转发。我们先留有这个概念。
# 通讯原理
Native做中转的话需要一个契机,就是WeixinJSBridge,WeixinJSBridge提供了渲染层与Native、Native与逻辑层之间消息通信的机制。机制是机制,那么具体的通讯手段是什么呢?
根据不一样的客户端通讯手段是不一样的,统一的机制为了更好的兼容不同的客户端,具体的通讯手段分为几种。
内置组件中有部分组件是利用到客户端原生提供的能力,既然需要客户端原生提供的能力,那就会涉及到渲染层与客户端的交互通信。这层通信机制在 iOS 和安卓系统的实现方式并不一样,iOS 是利用了 WKWebView 的提供 messageHandlers 特性,而在安卓则是往 WebView 的 window 对象注入一个原生方法,最终会封装成 WeiXinJSBridge 这样一个兼容层。在微信开发者工具中则是使用了websocket进行了封装。
在微信小程序执行过程中,Native层,也就是客户端层分别向渲染层与逻辑层注入WeixinJSBridge以达到线程通讯的目的,前面章节中在分析渲染层结构和逻辑层结构中我们也都看到了其中WeixinJSBridge的<script>标记注入,WeixinJSBridge提供了如下几个方法:
invoke- 调用Native API,以api方式调用开发工具提供的基础能力,并提供对应api执行后的回调。invokeCallbackHandler- Native 传递 invoke 方法回调结果on- 用来收集小程序开发者工具触发的事件回调publish- 渲染层发布消息,用来向逻辑业务层发送消息,也就是说要调用逻辑层的事件方法subscribe- 订阅逻辑层消息subscribeHandler- 视图层和逻辑层消息订阅转发setCustomPublishHandler- 自定义消息转发
上面说到初始化过程中Native层理论上是微信客户端,分别在视图层和业务逻辑层注入了WeixinJSBridge,但是由于运行环境的关系,比如我们现在正用的微信开发者工具来说,它并不是微信客户端,并没有Native。那么他是用什么手段进行线程与线程之间的通讯的呢?是Websocket。并且微信开发者工具在WeixinJSBridge的基础上进行了上面几个方法的重构,以达到调用微信开发者工具(IDE)功能目的。
那么是哪几个方法进行了重写呢,在源码中我们找到答案:

那我们就来继续探索一下,这几个方法。
首先这几个方法在微信开发者工具应用的文件夹中可以找到,目录为Contents/Resources/app.nw/js/extensions/pageframe/index.js。我们找到打开之后里面的代码依然是压缩的。这里使用js-beautify工具美化一下这个压缩的代码,以便于我们进行解析。
$ js-beautify index.js -o beautifyIndex.js
这样的话目录中就会多处一个美化后的js文件。建议把目标文件拷贝出来再进行操作。
打开这个“美化”之后的js文件后,发现依然还是很难读懂,哈哈,暂时我也没啥办法,有人认识小程序部门的人看看能不能要一份源码,记得发我一份。
在讲这几个方法之前我们先确认一个事情,就是微信开发者工具的通讯机制真的是websocket吗,我们从源码的角度来看一下。
# Websocket
首先我们找到上面所说的Websocket模块源码

可以看到模块内包含了websocket的创建、建立连接,收发数据,并且模块开放了一些方法,比如send、connect、registerCallback、removeCallback。
websocket建立连接了之后就是进行通讯协议约定,就像火车一样,你买的不一样的火车票,同样的火车就会拉你去不一样的地方。因为我们约定了不一样的火车票要去不一样的地方。
# invoke
invoke主要是用来 调用 Native API,在微信开发者工具中以api方式调用开发者工具提供的基础能力。
view层会统一向websocket服务发送command: WEBVIEW_INVOKE的命令,根据参数中的api值来确定调用开发者工具具体的api方法。调用完毕后,websocket服务向view层发送command: WEBVIEW_INVOKE_CALLBACK命令,渲染层根据此标识知道api调用完毕,然后执行对应的回调。
用过websocket的时候一样要约定一些通讯协议,比如PUBLISH_MAP_DATA: { k: v }。所上面的将的command就是这样约定的通讯协议来区分功能。

# on
当小程序开发者工具要触发渲染层的某个动作时,借助websocket服务向渲染层发送command: WEBVIEW_ON_EVENT命令,表示这个命令是来自开发者工具的。然后通过eventName来告诉渲染层执行什么事件方法。

# publish
用来向逻辑层发送消息,也就是说要调用逻辑层的事件方法,比如绑定的事件函数等等。
该过程涉及到双线程的通信,渲染层通过websocket服务触发逻辑层的对应事件方法。需要强调的是:
该方法没有收集执行的回调,它只是用来通知逻辑层调用指定的方法,并没有回调函数。
渲染层层向websocket服务发送command: WEBVIEW_PUBLISH的命令,websocket服务接到该命令知道是向逻辑层传递的消息,就直接向其转发消息。逻辑层收到消息后,根据消息参数的eventName值确定具体调用哪一个方法。
感觉是不是要和上一节连接上了,上一节事件系统中,渲染层出发事件后经过一系列转换和组装,最终适用sendData方法向渲染层发送数据,这里就是通过publish进行发送的。这样的话我们就把流程又继续串联了起来。先看一下publish的方法。

# subscribe
监听逻辑层触发的事件回调,和publish配套,一个发布,一个监听,就像javascript里的发布订阅模式。
渲染层执行回调的时机是收到来自websocket服务的command: APPSERVICE_PUBLISH命令,通过eventName来确定要执行具体记录的事件回调方法。来看一下:

# 总结
我们继续上一节,先重温一下上一节的流程图,然后进行串联起来。

中间sendData模块就是调用了WeixinJSBridge中的publish方法。发送了command: WEBVIEW_PUBLISH命令,数据结构大概如下

当渲染层把command: WEBVIEW_PUBLISH命令发送给逻辑层后,逻辑层需要去监听这条来自渲染层的命令,这条命令需要经过Native层转发,随后逻辑层接收。
我们来看一下渲染层是如何进行接收的。

这样的话就完成了从事件声明绑定、用户触发事件到逻辑层事件触发的单边流程。
抽象了一个流程图,我们来看一下:

触发了逻辑层的事件函数后还需要把data数据返回到渲染层。返回的时候逻辑层触发sendData方法,然后渲染层通过subscribe进行监听,通过记录的eventName和触发事件时候记录的回调函数来判断是哪个事件被触发了。
渲染获取动态数据后那么就开始走编译虚拟DOM的流程了,就连接上了前面的编译虚拟DOM的章节。
