一行经脉让你看懂 Weex Runtime 的任督二脉

整个研究主要分为三个部分,第一个部分研究weex初始化的脉络,探寻一下需要注意的细节。第二个部分研究一下业务bundle初始化的过程,真实的计算是在哪里发生的。第三个部分研究一下JS Framework的脉络走向,以及Native到底是如何桥接到JS Framework上的。

研究Service方案,是否符合预期。

分析视角为iOS + JavaScript

先从两端的脉络开始,梳理清楚之后,再做下一步的研究分析

weex runtime 初始化脉络研究

SDK初始化时比业务先注入js framework,如下是framework.js注入的顺序:

  • Native初始化(js framework)initSDKEnvironment,也提供了initSDKEnvironment:(NSString *)script来注入自己的framework不过一般基本用不上。

  • WXBridgeManager executeJsFramework

  • WXBridgeContext executeJsFramework

  • WXBridgeProtocol __jsBridge = [[WXJSCoreBridge alloc] init]

  • JSContext 扩展了很多属性,比如name,WXEnvironment等

  • WXBridgeContext registerGlobalFunctions 注册Native module,component等,给JSContext扩展callback,全部转换成数字id,每一个module,component等都有其对应的id。

  • JSContext evaluateScript 执行js framework.js代码

这说明一个问题:weex-framework.js 是全局共享的,只初始化一次,业务bundle打开的WXVC就算销毁了 (weex instance销毁),JSContext内的JS Framework也不会销毁。

业务bundle 初始化脉络研究

WXSDKInstance来负责注入业务bundle

  • WXSDKInstance renderWithURL 注入业务bundle url

  • WXSDKInstance _renderWithRequest

  • WXSDKInstance _renderWithMainBundleString

  • WXBridgeManager createInstance

  • WXBridgeContext createInstance

  • WXBridgeContext callJSMethod 传入@”createInstance”,以及组织参数args @[instance, temp, options ?: @{}, data],data就是业务bundle JavaScript字符串

  • 判断frameworkLoadFinished是否准备就绪,准备就绪调用WXJSCoreBridge 的callJSMethod 传入@”createInstance”,args,如果没有准备就绪,添加进_methodQueue。

  • JSContext globalObject invokeMethod 方法,获取之前初始化时JSContext的“全局对象”引用,类似浏览器的“window”对象,也就是执行weex-framework.js中的createInstance方法,接收的参数是@[instance, temp, options ?: @{}];

  • 从weex-framework.js中的createInstance又调入到Vue platforms/weex/framework.js 中的createInstance,

  • 最后 const result = new Function(…globalKeys)执行JS Script字符串,在业务中可以使用的weex,Vue也是从这里注入的

WXSDKInstance来负责销毁注入的业务bundle

  • WXSDKInstance destroyInstance 根据instanceId销毁当前的WX实例

  • WXBridgeManager destroyInstance

  • WXBridgeContext destroyInstance

  • WXBridgeContext callJSMethod 传入@”destroyInstance”和instance id

  • JSContext globalObject invokeMethod

这说明一个问题:创建UI界面的计算是在JavaScript这边的,创建和销毁阶段都是由Native主动调起JavaScript的方法,传入必要的参数,看来重点还是得研究weex-framework.js里,怎么实现计算又CallNative去渲染界面,代理其他事件等等。

JS Framework 脉络研究

运行在JSCore中的环境,究竟从哪里开始,globalObject对象中究竟有什么对象?

入口从html5/render/native/index.js开始

JS Framework 初始化

  • weex 仓库 render/native/index.js 调用 runtime/init.js 里的 init函数

  • weex 仓库 runtime/task-center.js initTaskHandler

  • vue 仓库 platforms/weex/framework.js init,传入config

运行时从Native端调用createInstance开始。

  • weex 仓库 runtime/init.js createInstance

  • weex 仓库 runtime/service.js createServices

  • vue 仓库 src/platforms/weex/framework.js createInstance

  • vue 仓库 createInstance最后sendTasks实际上是调用 weex仓库 runtime/config.js 的sendTasks,而这个方法传递给callNative

这个callNative方法是什么?是Native注册的block,这是JS Framework 与 Native 连接脉络的最后一步。

JSValue* (^callNativeBlock)(JSValue *, JSValue *, JSValue *) = ^JSValue*(JSValue *instance, JSValue *tasks, JSValue *callback){
    NSString *instanceId = [instance toString];
    NSArray *tasksArray = [tasks toArray];
    NSString *callbackId = [callback toString];
    
    WXLogDebug(@"Calling native... instance:%@, tasks:%@, callback:%@", instanceId, tasksArray, callbackId);
    return [JSValue valueWithInt32:(int32_t)callNative(instanceId, tasksArray, callbackId) inContext:[JSContext currentContext]];
};
_jsContext[@"callNative"] = callNativeBlock;

再来看看运行时的参数和返回值,转成了对应的Native上的数值。

这说明在“全局”环境中存在比如createInstance方法的,也有callNative。

业务bundle中的组件是如何执行的,脉络研究

import Hello from './Hello.vue';
Hello.el = '#app';
new Vue(Hello);
  • (在createInstance中通过new Function执行字符串的方式)new Vue(Hello); 组件new Vue,开始进入vue-runtime.js的流程

  • Vue类在Vue仓库 platforms/weex/framework.js createVueModuleInstance被扩展,增加了$document,$instanceId等

  • Vue进行组合计算,从_init开始

    Vue.prototype._init = function (options) {
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && config.performance && perf) {
          perf.mark('init');
        }
    
        var vm = this;
        // a uid
        vm._uid = uid++;
        // a flag to avoid this being observed
        vm._isVue = true;
        // merge options
        if (options && options._isComponent) {
          // optimize internal component instantiation
          // since dynamic options merging is pretty slow, and none of the
          // internal component options needs special treatment.
          initInternalComponent(vm, options);
        } else {
          vm.$options = mergeOptions(
            resolveConstructorOptions(vm.constructor),
            options || {},
            vm
          );
        }
        /* istanbul ignore else */
        if (process.env.NODE_ENV !== 'production') {
          initProxy(vm);
        } else {
          vm._renderProxy = vm;
        }
        // expose real self
        vm._self = vm;
        initLifecycle(vm);
        initEvents(vm);
        initRender(vm);
        callHook(vm, 'beforeCreate');
        initState(vm);
        initInjections(vm);
        callHook(vm, 'created');
    
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && config.performance && perf) {
          vm._name = formatComponentName(vm, false);
          perf.mark('init end');
          perf.measure(((vm._name) + " init"), 'init', 'init end');
        }
    
        if (vm.$options.el) {
          vm.$mount(vm.$options.el);
        }
    };

这段代码其实可以忽略,总之是从_init开始,走完Vue提供的生命周期,events,各种hook的流程,比如div组件在Native上会注册成一个Component,那么JS的Div如何与Native的Div Component对应上?

在完成:

const result = new Function(...globalKeys)
return result(...globalValues)

执行完业务bundle.js(new Vue)之后,会调用callNative方法,将参数{ module: ‘dom’, method: ‘createFinish’, args: [] }发送到Native端

  • WXJSCallNative block

  • WXBridgeContext invokeNative

    for (NSDictionary *task in tasks) {
        NSString *methodName = task[@"method"];
        NSArray *arguments = task[@"args"];
        if (task[@"component"]) {
            NSString *ref = task[@"ref"];
            WXComponentMethod *method = [[WXComponentMethod alloc] initWithComponentRef:ref methodName:methodName arguments:arguments instance:instance];
            [method invoke];
        } else {
            NSString *moduleName = task[@"module"];
            WXModuleMethod *method = [[WXModuleMethod alloc] initWithModuleName:moduleName methodName:methodName arguments:arguments instance:instance];
            [method invoke];
        }
    }
    
    [self performSelector:@selector(_sendQueueLoop) withObject:nil];
  • WXModuleMethod 传入JS定义的module dom

  • WXModuleMethod instance 调用 invoke

Service 方案脉络研究

梳理一下]Service注册的方案,看看它是什么

  • WXSDKEngine registerService 注册Service Name ,脚本,options

  • WXBridgeManager registerService

  • WXServiceFactory registerServiceScript

  • 组织一个数据结构 { name: name, options: options, script: WXServiceFactory registerService }

最终成为的结构为@””:


;(function(service, options){
    \n;
    serviceScript (我们的脚本)
    \n
})({
    register: global.registerService,
    unregister: global.unregisterService
}, {
    "serviceName": name,
    ...options
});
  • WXBridgeContext executeJsService

  • JSContext evaluateScript 执行这段脚本

这说明一个问题,service是注册到了weex-framework.js提供的registerService方法里了,我记得注册使用的脚本如下:


service.register(options.serviceName,{
    created: function(){
    
    },
    refresh: function(){
    
    },
    destroy: function(){
    
    }
})

不过WXSDKEngine也提供了销毁的方法,这个是可以卸载的,不过怎么卸载,又到了weex-framework.js里了。既然service跟runtime是一个平行的级别,如果不卸载,那么将永远的存在于内存中与runtime一样,是一个可以共享的区域。

从JS Framework的脉络来看,入口实现在 html5/runtime/service.js中。

export function register (name, options) {
    if (has(name)) {
        console.warn(`Service "${name}" has been registered already!`)
    } else {
        options = Object.assign({}, options)
        services.push({ name, options })
    }
}

name 就是service name,options是之前定义的一组对象:

{
    created: function(){
    
    },
    refresh: function(){
    
    },
    destroy: function(){
    
    }
}

看起来这一步都只是将定义的service结构存储在数组里,那么weex是如何调用的?

  • createInstance,这个方法是由Native调用的

  • createServices

    services.forEach(({ name, options }) => {
        if (process.env.NODE_ENV === 'development') {
            console.debug(`[JS Runtime] create service ${name}.`)
        }
        const create = options.create
        if (create) {
            const result = create(id, env, config)
            Object.assign(serviceMap.service, result)
            Object.assign(serviceMap, result.instance)
        }
    })
    

createServices函数中可以获取到create创建阶段,在这个循环中调用实现,这也说明,在create中可以获取到env平台信息,这也意味着可以抹平iOS Android差异了,至于result,也就是return出来的。

接着往下看,在createInstance函数中:

env.services = createServices(id, env, runtimeConfig)
instanceMap[id] = env

return frameworks[info.framework].createInstance(id, code, config, data, env)

既然知道了createInstance函数是由Native调用,那么frameworks[info.framework]这个framework决定了启用什么来渲染,也就是html5/framework/index.js 下指定的几种渲染框架。

(重要:目前v0.10.0版本Vue-runtime没有包service给new Function,不用想着使用了,只可以注册。)

    原文作者:闪电矿工翻译组
    原文地址: https://segmentfault.com/a/1190000008517448
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞