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<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,这个 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 的代码。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容