本文从编写的weex代码出发,看看它最终怎么到达端上的
Weex整体结构
可见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()