整个研究主要分为三个部分,第一个部分研究weex初始化的脉络,探寻一下须要注意的细节。第二个部分研究一下业务bundle初始化的过程,真实的计算是在哪里发生的。第三个部分研究一下JS Framework的脉络走向,以及Native究竟是如何桥接到JS Framework上的。html
研究Service方案,是否符合预期。vue
分析视角为iOS + JavaScripthtml5
先从两端的脉络开始,梳理清楚以后,再作下一步的研究分析数组
SDK初始化时比业务先注入js framework,以下是framework.js注入的顺序:浏览器
Native初始化(js framework)initSDKEnvironment,也提供了initSDKEnvironment:(NSString *)script来注入本身的framework不过通常基本用不上。weex
WXBridgeManager executeJsFramework数据结构
WXBridgeContext executeJsFrameworkapp
WXBridgeProtocol __jsBridge = [[WXJSCoreBridge alloc] init]框架
JSContext 扩展了不少属性,好比name,WXEnvironment等dom
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也不会销毁。
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去渲染界面,代理其余事件等等。
运行在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。
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注册的方案,看看它是什么
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,不用想着使用了,只能够注册。)