Weex-从代码到端上发生了什么?

1,069 阅读4分钟

本文从编写的weex代码出发,看看它最终怎么到达端上的

Weex整体结构

image
可见weex整体分为两部分:

  • 存在server上的业务代码
  • 搭建在端内的weex环境

本地写vue代码

一段典型的vue,也就是weex代码如下(我抄别人的)

<template>
  <foo a="{{x}}" b="1" class="bar"></foo>
</template>

<style>
  .bar {width: 200; height: 200}
</style>

<script>
  module.exports = {
    data: function () {
      return {x: 100}
    }
  }
</script>

打包

vue代码会被转一下

define('@weex-component/main', function () {
  module.exports = {
    data: function () {
      return {x: 100}
    }
  }
  module.template = {
    type: "foo",
    attr: {
      a: function () {return this.x},
      b: 1,
      classname: ['bar']
    }
  }
  module.style = {
    bar: {width: 200, height: 200}
  }
})
bootstrap('@weex-component/main')

然后,webpack会继续进行一些压缩、包装的工作,成为一个 js bundle。上传到服务器。

不混入vue框架本身

在web开发中,框架代码是要和业务代码一起打包到bundle中的,根本原因是浏览器的环境我们无法定制,如果不带上框架代码,vue代码就跑不起来。
但在Weex中,有能力也必须在端上搭建一些东西,索性就把框架部分放到端上了,这样能大大减少服务器上 bundle 的体积。

拉包

上传到服务器后,等端再启动加载weex,就需要拉取最新的包,这也是动态化的基础。至于拉包的版本,搞一个类似“MIS系统”的配置就行了。

包缓存

通常情况下,weex版本的更新是不会像web那么频繁的,那么端上就能做一层离线缓存,既省流量又能离线可用。

让JS跑起来

包拉下来了,首先要让JS跑起来,提供引擎。原生是不行的,这就是Weex SDK提供的最基础能力,就是图中的 JSCore/V8。

套上framework

跑起来以后呢?前面说过,bundle没有把vue的框架打包进来,这部分是在SDK环境中的。bundle拉下来后,先套上framework,叫做weex-vue-framework。
考虑下React的思路,react本身只提供Virtual DOM相关的部分,直到patch出节点操作(增删改)命令,patch后的命令执行根据平台不同交给不同其他模块,比如DOM的react-dom、跨端的react-native。
这个 weex-vue-framework 也和 vue.js 几乎一样,不同的是vue.js输出DOM节点,weex-vue-framework 则patch出对原生控件的操作命令。

操作/响应Native

源码经过框架包装,需要进一步和原生交互:

  • patch出节点操作命令(增删改)--> 执行对原生控件的操作
  • 调用native能力,比如拍照、打电话
  • 响应原生控件触发的事件

在Weex SDK中,这部分由 JS-Native Bridge 实现。bridge 通过一个global维护所有交互方法,包括两端的注册和调用。具体列表见后面。
其中提供两个重要方法:callNative、callJS。

js调native-callNative

callNative由原生代码实现,注册到global上,供JS调用,向native发送指令,例如 rendering, networking, authorizing和其他客户端侧的 toast 等API,实现前两种交互。
js 主动调用 native 都是调用 taskCenter 的 send 方法。taskCenter 是在 Document 初始化时,绑定到 Document 对象上的。

taskCenter.send->sendTasks->taskCenter.callbackManager.add(v)->global.callNative(...args)

比如 patch 出一个创建节点的命令,进行类似调用:

callNative({
  module: 'dom',
  method: 'addElement',
  args: ['_root', {
    ref: '2',
    type: 'text',
    attr: { value: '哟' },
    style: { textAlign: 'center', fontSize: 200 }
  }]
})

当然js调native不止callNative一种方法,这只是通用的方式。实际上bridge是个注册机制,native把自己的能力注册到global上,通常以call开头,当某个call没注册

callAddElement: function()
callNative: function()
callNativeComponent: function()
callNativeModule: function()

native调js-callJS

callJS由JS实现,用于Native向JS发送指令,实现第三种交互。
当原生控件触发事件,native 发起 fireEvent,调用 callJS 方法,将method name 和 参数 打包成 json 传递给 js。
js 用 receiveTasks接收后分发给 framework 的receiveTasks。receiveTasks再调用到 fireEvent,根据 instanceID 找到对应的 instance,调用 instance.document 的 fireEvent。

callJS-> receiveTasks -> fireEvent -> instance.document.fireEvent -> if(domChanges){updateElement} -> element.fireEvent

附:global所有方法

    callAddElement: function()

    callJS: function()

    callNative: function()

    callNativeComponent: function()

    callNativeModule: function()

    clearIntervalWeex: function()

    clearTimeoutWeex: function()
    createInstance: function()
    destroyInstance: function()
    getRoot: function()
    nativeLog: function()
    receiveTasks: function()

    refreshInstance: function()

    registerComponents: function()

    registerMethods: function()

    registerModules: function()

    registerService: function()

    setIntervalWeex: function()

    setTimeout: function()

    setTimeoutWeex: function()
    unregisterService: function()

参考资料