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. 注册一个 实现 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
  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 时,会调用 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;

  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 添加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 方法。有

  1. destroyInstance
  2. refreshInstance
  3. receiveTasks
  4. 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。

总结

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

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

最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容