一行經脈讓你看懂 Weex Runtime 的任督二脈

整個研究主要分爲三個部分,第一個部分研究weex初始化的脈絡,探尋一下須要注意的細節。第二個部分研究一下業務bundle初始化的過程,真實的計算是在哪裏發生的。第三個部分研究一下JS Framework的脈絡走向,以及Native究竟是如何橋接到JS Framework上的。html

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

分析視角爲iOS + JavaScripthtml5

先從兩端的脈絡開始,梳理清楚以後,再作下一步的研究分析數組

weex runtime 初始化脈絡研究

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也不會銷燬。

業務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,不用想着使用了,只能夠註冊。)

相關文章
相關標籤/搜索