Weex-Vue-Framework 初始化

初始化

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
}
  1. 设置 版本号,app name 等信息
  2. 初始化 SDK
  3. 注册一个 实现 WXImgLoaderProtocolhandler 图片下载。
    因为 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
  1. 存储/删除 instance。
  2. 管理 WXBridgeManager
WXBridgeManager
  1. 创建 JS 线程
  2. 转发 native 调用 JS的事件到 js 线程执行。
  3. 执行 js framework 的代码
  4. 注册 component,modules,services
  5. 调用 js 代码

SDK 里面会创建2个 WXBridgeManager,

  1. WXSDKManager.bridgeMgr 会 alloc 一个
  2. BridgeManager 和 js 交互 会创建一个名字为 com.taobao.weex.bridge 的线程,
    创建这个线程执行的 target 是一个 单例的 WXBridgeManager。

所以 WXSDKManager.bridgeMgr 负责和 js 交互,单例 BridgeManager 只负责跑一个 Runloop 让线程一直存在,不自动退出。

这个玩法 WXComponentManager 也是类似。
instance 在创建 component 时,会调用 WXComponentManagerinitWithWeexInstance create 一个WXComponentManager 并持有。

WXComponentManager 的作用是 专门解析 从 js 传过来的component 的 json。也有一个独立的线程专门来处理,

也是创建了一个WXComponentManager单例来作为 线程的 target,让线程不自动退出。

WXBridgeManager 发送的 js 事件,都会转到 com.taobao.weex.bridge 线程,调用
WXBridgeContext 来执行对应的事件。

WXBridgeContext

可以理解为 bridge 执行的环境。一直执行在 jsbridge 线程。 根据执行环境,确定使用哪个 bridge。

- (id)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,这个 jsBridgegetter 方法里面初始化。
会根据是否 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 之后,调用 WXJSCoreBridgeexecuteJSFramework 方法,执行 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;

  1. 执行 前面缓存调用的注册 服务 js 方法,
  2. 读取 js 的版本号
  3. 执行 前面缓存的 执行 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 里面的方法。

  1. 给 config 添加 frameworks 属性。
  2. 注册 services
  3. 设置 nativeConsole 输出方法。
  4. 设置 framework 的版本号和 transformer 版本号。

transformer:weex-transformer,将 we 文件转换为 JSBundle 文件的转换器。

  1. 调用 init(config) 初始化 frameworks,返回 globalMethods
  2. 设置 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
}
  1. initTaskHandler()
  2. 遍历 frameworks ,执行 framework 的 init(config)
  3. 添加 'registerComponents', 'registerModules', 'registerMethods' 方法。
  4. 添加 'destroyInstance', 'refreshInstance', 'receiveTasks', 'getRoot' 方法。
  5. 添加接收 native 的 callJS 方法, 调用 framework 的 receiveTasks 接收。
  6. 返回 上面 注册号的 所以方法。

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

  1. componentHandler = global.callNativeComponent
  2. 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 添加registerComponentsregisterModulesregisterMethods,注册方法。调用每一个 framework 对应的方法。

当 methodName === 'registerComponents' 时,调用 checkComponentMethods (components)

checkComponentMethods (components) 的作用就是 判断 注册的 components,如果 component 有 typemethod 属性,则执行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 方法。有

  1. destroyInstance
  2. refreshInstance
  3. receiveTasks
  4. getRoot

调用的时候判断如果是refreshInstance 或者 destroyInstance,则还会调用 servicerefreshdstroy
注册 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.jsentry-runtime-factory.js,打包后 对应 index.jsfactory.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

总结

  1. native 先注册 components,modules,handlers,但是 js 没有初始化好,所以先缓存起来;
  2. native 在调用 js 方法的时候,初始化 js 的执行环境,执行 framework 的 js 代码;
  3. js 初始化完成后,再执行前面缓存的 js 调用方法

SDK 就初始化完成了,WXSDKManager 是单例,所以 整个 app,sdk 只初始化一次,
之后就开始加载 JSBundle 的代码。

你可能感兴趣的:(Weex-Vue-Framework 初始化)