One line of meridians enables you to understand Ren Du’s two meridians of weex runtime

Time:2020-1-21

The whole research is mainly divided into three parts. The first part studies the context of weex initialization and explores the details that need attention. The second part studies the process of business bundle initialization, where the real computation occurs. The third part studies the context of JS framework and how native bridge to JS framework.

Study whether the service scheme meets the expectation.

Analysis perspective is IOS + JavaScript

First, start from the context of both ends, and then do the next research and analysis

Research on the initialization context of weex runtime

During SDK initialization, JS framework is injected before business. The following is the sequence of framework.js injection:

  • The native initialization (JS framework) initsdkenenvironment also provides initsdkenenvironment: (nsstring *) script to inject its own framework, but it is generally not used.

  • WXBridgeManager executeJsFramework

  • WXBridgeContext executeJsFramework

  • WXBridgeProtocol __jsBridge = [[WXJSCoreBridge alloc] init]

  • Jscontext extends many properties, such as name, wxenvironment, etc

  • Wxbridgecontext registerglobalfunctions register native module, component, etc., extend callback to jscontext, and convert them into digital IDs. Each module, component, etc. has its corresponding ID.

  • Jscontext evaluatescript executes JS framework.js code

This shows a problem: weex-framework.js is shared globally. It is initialized only once. Even if wxvc opened by business bundle is destroyed (weex instance is destroyed), JS framework in jscontext will not be destroyed.

Research on initialization context of business bundle

Wxsdkinstance is responsible for injecting business bundle

  • Wxsdkinstance renderwithurl injection business bundle URL

  • WXSDKInstance _renderWithRequest

  • WXSDKInstance _renderWithMainBundleString

  • WXBridgeManager createInstance

  • WXBridgeContext createInstance

  • Wxbridgecontext calljsmethod passed in @ “createinstance”, and args @ [instance, temp, options?: @ {}, data], data is the business bundle JavaScript string

  • Judge whether the frameworkloadfinished is ready to call the call jsmethod of wxjscorebridge to pass in @ “createinstance”, args. If not, add it to “methodqueue”.

  • The JSContext globalObject invokeMethod method is used to obtain the “global object” reference of the JSContext during the previous initialization. It is similar to the “window” object of the browser, that is, to execute the createInstance method in weex-framework.js. The received parameters are @[instance, temp, options?: @ {}];

  • From createinstance in weex-framework.js to createinstance in Vue platforms / weex / framework.js,

  • Finally, const result = new function (… Globalkeys) executes the JS script string. The weex and Vue that can be used in the business are also injected from here

Wxsdkinstance is responsible for destroying the injected business bundle

  • Wxsdkinstance destroyinstance destroys the current Wx instance according to the instanceid

  • WXBridgeManager destroyInstance

  • WXBridgeContext destroyInstance

  • Wxbridgecontext calljsmethod passed in @ “destroyinstance” and instance ID

  • JSContext globalObject invokeMethod

This shows a problem: the calculation of creating UI is on the JavaScript side. In the creation and destruction phase, native takes the initiative to call up the JavaScript method and pass in the necessary parameters. It seems that the focus is still to study how to implement the calculation in weex-framework.js and call native to render the interface and proxy other events.

Research on JS framework context

Where does the environment running in jscore start and what are the objects in the global object object?

The entry starts with HTML5 / render / native / index.js

JS framework initialization

  • Weex warehouse render / native / index.js calls init function in runtime / init.js

  • Weex warehouse Runtime / task-center.js inittaskhandler

  • Vue warehouse platforms/weex/framework.js init, pass in config

The runtime starts with a call to createinstance on the native side.

  • Weex warehouse Runtime / init.js createinstance

  • Weex warehouse Runtime / service.js createservices

  • Vue warehouse Src / platforms / weex / framework.js createinstance

  • Vue warehouse createinstance finally sendtasks is actually to call sendtasks of weex warehouse Runtime / config.js, and this method is passed to callnative

What is the callnative method? It is a native registered block, which is the last step of connecting JS framework and 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;

Let’s look at the runtime parameters and return values, which are converted to the values on the corresponding native.

This indicates that there are both the createinstance method and the callnative method in the “global” environment.

Research on how components in business bundle are executed

import Hello from './Hello.vue';
Hello.el = '#app';
new Vue(Hello);
  • New Vue (Hello); component new Vue, start the process of entering vue-runtime.js

  • Vue class is extended in Vue warehouse platforms / weex / framework.js createvuemoduleinstance, adding $document, $instanceid, etc

  • Vue performs a combined calculation, starting with “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);
        }
    };

This code can be ignored. In a word, it starts from “init” and completes the life cycle, events, and various hook processes provided by Vue. For example, div components will be registered as a component in native. How does the div of JS correspond to native div components?

Completion:

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

After executing the business bundle.js (New Vue), the callnative method will be called to send the parameter {module: ‘DOM’, method: ‘createfinish’, args: []} to the native side

  • 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 passes in the module DOM defined by JS

  • Wxmodulemethod instance calls invoke

Research on service scheme context

Comb] the scheme of service registration to see what it is

  • Wxsdkengine registerservice register service name, script, options

  • WXBridgeManager registerService

  • WXServiceFactory registerServiceScript

  • Organize a data structure {Name: name, options: options, script: wxsservicefactory registerservice}

The resulting structure is @ ”:

;(function(service, options){
    \n;
    Servicescript (our script)
    \n
})({
    register: global.registerService,
    unregister: global.unregisterService
}, {
    "serviceName": name,
    ...options
});
  • WXBridgeContext executeJsService

  • Jscontext evaluatescript executes this script

This shows a problem. Service is registered in the registerservice method provided by weex-framework.js. I remember the script used for registration is as follows:


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

However, wxsdkengine also provides the method of destruction, which can be uninstalled. However, how to uninstall is in weex-framework.js. Since service and runtime are at a parallel level, if they are not uninstalled, they will always exist in memory, just like runtime, which is a shared area.

From the perspective of JS framework, the entry is implemented in 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 is service name, and options are a set of previously defined objects:

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

It seems that this step is just to store the defined service structure in the array, so how does weex call?

  • Createinstance, which is called by 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)
        }
    })
    

In the createServices function, you can get the create creation phase and call it in this cycle. This also shows that you can get the information of Env platform in create, which means that iOS Android difference can be smoothed out, as for result, that is, return comes out.

Next, in the createinstance function:

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

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

Now that you know that the createinstance function is called by native, the framework [info. Framework] determines what to enable for rendering, that is, several rendering frameworks specified under HTML5 / framework / index.js.

(important: at present, there is no package service for new Function in Vue-runtime in v0.10.0. You don’t need to think about using it. You can only register.)