初始化
native
+ (void)initWeexSDK
{
[WXAppConfiguration setAppGroup:@"AliApp"];
[WXAppConfiguration setAppName:@"OperatorWeex"];
[WXAppConfiguration setAppVersion:@"1.8.3"];
[WXAppConfiguration setExternalUserAgent:@"ExternalUA"];
[WXSDKEngine initSDKEnvironment];
[WXSDKEngine registerHandler:[WXImgLoaderDefaultImpl new] withProtocol:@protocol(WXImgLoaderProtocol)];
[WXSDKEngine registerHandler:[WXEventModule new] withProtocol:@protocol(WXEventModuleProtocol)];
[WXSDKEngine registerComponent:@"select" withClass:NSClassFromString(@"WXSelectComponent")];
[WXSDKEngine registerModule:@"event" withClass:[WXEventModule class]];
#ifdef DEBUG
[WXLog setLogLevel:WXLogLevelAll];
#endif
}
- 设置 版本号,app name 等信息
- 初始化 SDK
- 注册一个 实现
WXImgLoaderProtocol
的handler
图片下载。
因为 Weex 在 native 没有实现 图片下载功能。
initSDKEnvironment
[self registerDefaults];
[[WXSDKManager bridgeMgr] executeJsFramework:script];
注册 native 端的 module,components,handlers,然后执行 jsSDK 代码
registerDefaults
:
[self _registerDefaultComponents];
[self _registerDefaultModules];
[self _registerDefaultHandlers];
然后执行 framework 的 js 代码
[[WXSDKManager bridgeMgr] executeJsFramework:script];
这里 先调用的注册方法,因为 jsframework 还没有初始化完成,所以会先放在一个队列里面,等 framework 初始化完成后,在从队列中取出 执行。
这些注册方法都是先注册到本地,然后
registerComponents 和 registerDefaultModules 继续调用 [WXSDKManager bridgeMgr]
来注册到 js 的。
_registerDefaultHandlers
注册SDK 中已经默认实现的 handler。
registerModule
+ (void)registerModule:(NSString *)name withClass:(Class)clazz
{
WXAssert(name && clazz, @"Fail to register the module, please check if the parameters are correct !");
// 1. 遍历出 暴露给 js 的方法, 保存 module 的配置到 _moduleMap,
NSString *moduleName = [WXModuleFactory registerModule:name withClass:clazz];
// 2. 根据 config 生成 一个 dict,准备发送到 js。
NSDictionary *dict = [WXModuleFactory moduleMethodMapsWithName:moduleName];
// 3.
[[WXSDKManager bridgeMgr] registerModules:dict];
}
registerComponent
+ (void)registerComponent:(NSString *)name withClass:(Class)clazz withProperties:(NSDictionary *)properties
{
if (!name || !clazz) {
return;
}
WXAssert(name && clazz, @"Fail to register the component, please check if the parameters are correct !");
// 1. 遍历暴露方法,生成 config,保存 config 到 _componentConfigs
[WXComponentFactory registerComponent:name withClass:clazz withPros:properties];
// 2. 生成 一个 map
NSMutableDictionary *dict = [WXComponentFactory componentMethodMapsWithName:name];
dict[@"type"] = name;
// 3. 转发给 js
if (properties) {
NSMutableDictionary *props = [properties mutableCopy];
if ([dict[@"methods"] count]) {
[props addEntriesFromDictionary:dict];
}
[[WXSDKManager bridgeMgr] registerComponents:@[props]];
} else {
[[WXSDKManager bridgeMgr] registerComponents:@[dict]];
}
}
最后一个入参是否传@{@"append":@"tree"},如果被标记成了@"tree",那么在syncQueue堆积了很多任务的时候,会被强制执行一次layout。
WXModuleFactory
, WXComponentFactory
,WXHandlerFactory
这三个类 分别管理 module,component 和 handler。注册的时候,分别保存有对应的 配置信息。
其中 handler 只是实现 sdk 没有实现的功能,不需要注册到 js,module 和 component 需要注册到 js 中。
WXSDKManager
- 存储/删除 instance。
- 管理
WXBridgeManager
WXBridgeManager
- 创建 JS 线程
- 转发 native 调用 JS的事件到 js 线程执行。
- 执行 js framework 的代码
- 注册 component,modules,services
- 调用 js 代码
SDK 里面会创建2个 WXBridgeManager,
- WXSDKManager.bridgeMgr 会 alloc 一个
- BridgeManager 和 js 交互 会创建一个名字为
com.taobao.weex.bridge
的线程,
创建这个线程执行的 target 是一个 单例的 WXBridgeManager。
所以 WXSDKManager.bridgeMgr
负责和 js 交互,单例 BridgeManager 只负责跑一个 Runloop 让线程一直存在,不自动退出。
这个玩法 WXComponentManager
也是类似。
instance 在创建 component 时,会调用 WXComponentManager
的 initWithWeexInstance
create 一个WXComponentManager
并持有。
WXComponentManager
的作用是 专门解析 从 js 传过来的component 的 json。也有一个独立的线程专门来处理,
也是创建了一个WXComponentManager
单例来作为 线程的 target,让线程不自动退出。
WXBridgeManager 发送的 js 事件,都会转到 com.taobao.weex.bridge
线程,调用
WXBridgeContext
来执行对应的事件。
WXBridgeContext
可以理解为 bridge 执行的环境。一直执行在 jsbridge 线程。 根据执行环境,确定使用哪个 bridge。
- (id<WXBridgeProtocol>)jsBridge
{
WXAssertBridgeThread();
_debugJS = [WXDebugTool isDevToolDebug];
Class bridgeClass = _debugJS ? NSClassFromString(@"WXDebugger") : [WXJSCoreBridge class];
if (_jsBridge && [_jsBridge isKindOfClass:bridgeClass]) {
return _jsBridge;
}
if (_jsBridge) {
[_methodQueue removeAllObjects];
_frameworkLoadFinished = NO;
}
_jsBridge = _debugJS ? [NSClassFromString(@"WXDebugger") alloc] : [[WXJSCoreBridge alloc] init];
[self registerGlobalFunctions];
return _jsBridge;
}
WXBridgeContext
持有一个 jsBridge
,这个 jsBridge
在 getter
方法里面初始化。
会根据是否 debug 判断是加载 WXDebugger
或是 WXJSCoreBridge
。
这里 SDK 定义了 一个 WXBridgeProtocol
协议,任何一个 JSBridge 只要实现了该协议都可以使用。
在 初始化 好 jsBridge
后,就会调用 registerGlobalFunctions
注册 全局方法。
callNative
callAddElement
callNativeModule
callNativeComponent
WXJSCoreBridge
真正和 js 交互的类。
在 init()
方法里面 会给 js 环境 注入一些全局方法和属性
WXEnvironment
setTimeout
setTimeoutWeex
setIntervalWeex
clearIntervalWeex
clearTimeoutWeex
btoa
atob
nativeLog
加上 registerGlobalFunctions 注册的全局方法,JSContext 总共有13个全局方法。
executeJSFramework
初始化 WXJSCoreBridge 之后,调用 WXJSCoreBridge
的 executeJSFramework
方法,执行 js 代码。
关系:
WXSDKManager(SDK)
->WXBridgeManager(主线程调用)
->WXBridgeContext(在 bridge 线程调用)
->WXJSCoreBridge(实现 bridgeProtocol,对应不同环境的 bridge)。
最后
self.frameworkLoadFinished = YES;
[self executeAllJsService];
JSValue *frameworkVersion = [self.jsBridge callJSMethod:@"getJSFMVersion" args:nil];
if (frameworkVersion && [frameworkVersion isString]) {
[WXAppConfiguration setJSFrameworkVersion:[frameworkVersion toString]];
}
//execute methods which has been stored in methodQueue temporarily.
for (NSDictionary *method in _methodQueue) {
[self callJSMethod:method[@"method"] args:method[@"args"]];
}
[_methodQueue removeAllObjects];
执行完 js 端 framework 代码后,设置 self.frameworkLoadFinished = YES;
- 执行 前面缓存调用的注册 服务 js 方法,
- 读取 js 的版本号
- 执行 前面缓存的 执行
jsCall
方法。
weex
executeJsFramework
调用 js 引擎 执行 js framework 代码。
js 端的入口在 html5/native/index.js
,
调用setup.js
传入 4个 framework:Vanilla,Vue,Rax,Weex
。```
1.setup.js
export default function (frameworks) {
const { init, config } = runtime
config.frameworks = frameworks
const { native, transformer } = subversion
for (const serviceName in services) {
runtime.service.register(serviceName, services[serviceName])
}
runtime.freezePrototype()
runtime.setNativeConsole()
// register framework meta info
global.frameworkVersion = native
global.transformerVersion = transformer
// init frameworks
const globalMethods = init(config)
// set global methods
for (const methodName in globalMethods) {
global[methodName] = (...args) => {
const ret = globalMethods[methodName](...args)
if (ret instanceof Error) {
console.error(ret.toString())
}
return ret
}
}
}
这里的代码比较简单,基本上都是调用 runtime 里面的方法。
- 给 config 添加 frameworks 属性。
- 注册 services
- 设置 nativeConsole 输出方法。
- 设置 framework 的版本号和 transformer 版本号。
transformer:weex-transformer,将 we 文件转换为 JSBundle 文件的转换器。
- 调用
init(config)
初始化 frameworks,返回 globalMethods - 设置 global methods
传入 init 的 config 对象在
runtime/config.js
中定义。
const config = {
Document, Element, Comment, Listener,
TaskCenter,
sendTasks (...args) {
return global.callNative(...args)
}
}
Document.handler = config.sendTasks
把 vdom 定义的 Document,Element,Comment
都传递到了 framework 中,
同时 还有 任务处理 类: Listener,TaskCenter,sendTasks
,并且把 Document.handler 赋值为 sendTasks。
2.init(config)
export default function init (config) {
runtimeConfig = config || {}
frameworks = runtimeConfig.frameworks || {}
initTaskHandler()
// Init each framework by `init` method and `config` which contains three
// virtual-DOM Class: `Document`, `Element` & `Comment`, and a JS bridge method:
// `sendTasks(...args)`.
for (const name in frameworks) {
const framework = frameworks[name]
framework.init(config)
}
// @todo: The method `registerMethods` will be re-designed or removed later.
; ['registerComponents', 'registerModules', 'registerMethods'].forEach(genInit)
; ['destroyInstance', 'refreshInstance', 'receiveTasks', 'getRoot'].forEach(genInstance)
adaptInstance('receiveTasks', 'callJS')
return methods
}
- initTaskHandler()
- 遍历 frameworks ,执行 framework 的 init(config)
- 添加
'registerComponents', 'registerModules', 'registerMethods'
方法。 - 添加
'destroyInstance', 'refreshInstance', 'receiveTasks', 'getRoot'
方法。 - 添加接收 native 的
callJS
方法, 调用 framework 的receiveTasks
接收。 - 返回 上面 注册号的 所以方法。
1、因为 weex 换成了 Vue 来驱动,所以 用 只看了 Vue framework 的init,其他应该类似。
vue-framework 的代码在 vue 仓库中。
2、注册的方法都保存在 methods
中,其本身默认就有三个方法
const methods = {
createInstance,
registerService: register,
unregisterService: unregister
}
这样 返回的 methods 就有:
createInstance,
registerService: register,
unregisterService: unregister
registerComponents
registerModules
registerMethods
destroyInstance
refreshInstance
receiveTasks
getRoot
callJS
3、这些全局方法 的实现。里面基本都是 再 分发到 每一个 framework 中。framework 需要实现上面这些方法。
3.initTaskHandler()
initTaskHandler
调用的是 task-center.js
中的 init()
。
主要是给 TaskCenter 添加 Dom 操作的方法。
createFinish: global.callCreateFinish,
updateFinish: global.callUpdateFinish,
refreshFinish: global.callRefreshFinish,
createBody: global.callCreateBody,
addElement: global.callAddElement,
removeElement: global.callRemoveElement,
moveElement: global.callMoveElement,
updateAttrs: global.callUpdateAttrs,
updateStyle: global.callUpdateStyle,
addEvent: global.callAddEvent,
removeEvent: global.callRemoveEvent
这些方法 都对应于 native 端 WXDomModule
暴露出来的 方法。
遍历 DOM_METHODS 注入到 TaskCenter。
const proto = TaskCenter.prototype
for (const name in DOM_METHODS) {
const method = DOM_METHODS[name]
proto[name] = method ?
(id, args) => method(id, ...args) :
(id, args) => fallback(id, [{ module: 'dom', method: name, args }], '-1')
}
但是 注意的是 源码中定义的DOM_METHODS
默认是调用 global 的,但是现在 native SDK 中这些方法只有 addElement
注册了,其他都没有注册,所以 global 对象是没有这些方法的,
所以 在遍历的时候把调用都改为 调用 fallback。fallback 会在 TaskCenter 初始化的时候传入。
sendTasks (...args) {
return global.callNative(...args)
}
最后给 TaskCenter 添加两个 handler
- componentHandler = global.callNativeComponent
- moduleHandler = global.callNativeModule
initTaskHandler( )方法初始化了13个方法(其中2个handler),都绑定到了prototype上
TaskCenter
的作用 主要是 从 js 发送消息到 native,方法就是 send。根据 type 类型,分别调用上面的 dom,component,module 方法。
send (type, options, args) {
const { action, component, ref, module, method } = options
args = args.map(arg => this.normalize(arg))
switch (type) {
case 'dom':
return this[action](this.instanceId, args)
case 'component':
return this.componentHandler(this.instanceId, ref, method, args, { component })
default:
return this.moduleHandler(this.instanceId, module, method, args, {})
}
}
4.genInit (methodName)
主要是给 global 添加registerComponents
,registerModules
,registerMethods
,注册方法。调用每一个 framework
对应的方法。
当 methodName === 'registerComponents' 时,调用 checkComponentMethods (components)
。
checkComponentMethods (components)
的作用就是 判断 注册的 components,如果 component 有 type
和 method
属性,则执行registerElement(name.type, name.methods)
。
registerElement (type, methods)
将 component 保存为 XElement,作为一个 Element 的扩展。
也就是说 客户端注册的有 method
的 component,都是以 XElement 的形式存在的。
XElement
:
input: function(props)
list: function(props)
recycler: function(props)
scroller: function(props)
textarea: function(props)
waterfall: function(props)
web: function(props)
checkComponentMethods
之后再调用 framework 内的registerComponents
。
1、registerComponents
:保存到components
2、registerModules
:保存到 modules
3、registerMethods
: 弃用了,vue 没有实现。
5.genInstance (methodName)
给 global 注册 instance 方法。有
- destroyInstance
- refreshInstance
- receiveTasks
- getRoot
调用的时候判断如果是refreshInstance
或者 destroyInstance
,则还会调用 service
的 refresh
和 dstroy
。
注册 service 就在 services
目录下,只有一个 BroadcastChannel
,这个服务在 vue 版本中也是弃用了。这里虽然调用也没什么卵用。
其他都是直接调用 framework 对应的方法。
6.adaptInstance('receiveTasks', 'callJS')
init 方法的最后,调用这个方法的作用是为了适配 native 调用方法。
native 调用 js,方法名都是 callJS
,framework 的接收方法都是 receiveTasks
,所以这里做一个适配。
weex-vue-framework
调用 framework.init(config)
传入的 config
,主要作用就是讲 weex 定义的一套东西 传递给 framework,
framework 负责解析 JSBundle,来适配 weex。
config:Document, Element, Comment, Listener,TaskCenter,sendTasks (...args) {}, frameworks
weex-vue
源码包含两部分,entry-framework.js
和entry-runtime-factory.js
,打包后 对应 index.js
和 factory.js
。
-
entry-framework.js
负责实现 weex 定义的 framework 需要实现的协议。 -
entry-runtime-factory
负责实现 对 JSBundle 的解析和渲染。
init
就在 entry-framework.js
。
init(cfg)
weex-vue 的 init 代码,就是把 参数 config 中 vdom 赋值到 render 上。
export function init (cfg) {
renderer.Document = cfg.Document
renderer.Element = cfg.Element
renderer.Comment = cfg.Comment
renderer.compileBundle = cfg.compileBundle
}
render 默认已经有 TextNode,instances,modules,components
。
总结
- native 先注册 components,modules,handlers,但是 js 没有初始化好,所以先缓存起来;
- native 在调用 js 方法的时候,初始化 js 的执行环境,执行 framework 的 js 代码;
- js 初始化完成后,再执行前面缓存的 js 调用方法
SDK 就初始化完成了,WXSDKManager 是单例,所以 整个 app,sdk 只初始化一次,
之后就开始加载 JSBundle 的代码。