本文研究58fair 动态化界面逻辑js JavascriptCore目标的创立与开释

58fair 动态化界面逻辑JavaScriptCore js对象的创建与释放

页面js

一个Fair动态化界面的逻辑js文件实例为:

GLOBAL['test1#1'] = (function(__initProps__) {
    const __global__ = this;
    return runCallback(function(__mod__) { //runCallback 在fair_jsbase.js中界说
        with(__mod__.imports) {
            function _Test5NetPluginState() {
                const inner = _Test5NetPluginState.__inner__;
                if (this == __global__) {
                    return new _Test5NetPluginState({
                        __args__: arguments
                    });
                } else {
                    const args = arguments.length > 0 ? arguments[0].__args__ || arguments : [];
                    inner.apply(this, args);
                    _Test5NetPluginState.prototype.ctor.apply(this, args);
                    return this;
                }
            }
            _Test5NetPluginState.__inner__ = function inner() {
                this._page = 0;
            };
            _Test5NetPluginState.prototype = {
                requestData: function requestData() {
                    const __thiz__ = this;
                    with(__thiz__) {
                        _page++;
                        FairNet.requestData({
                            method: 'GET',
                            url: 'https://wos2.58cdn.com.cn/DeFazYxWvDti/frsupload/3be6c61070d3b48c8165af5d18464c0e_hotel_list_data.json',
                            data: convertObjectLiteralToSetOrMap({
                                ['page']: _page,
                                ['pageName']: 'test1#1',
                            }),
                            success: function dummy(resp) {
                                if (resp == null) {
                                    return null;
                                }
                                let data = resp.__op_idx__('data');
                                setState('test1#1', function dummy() {});
                            }
                        });
                    }
                },
            };
            _Test5NetPluginState.prototype.ctor = function() {};;
            return _Test5NetPluginState();
        }
    }, []);
})(convertObjectLiteralToSetOrMap(JSON.parse('{}')));

其中runCallback办法在大局js fair_base.js中界说:

function runCallback(func, deps) { //
  const imports = {};
  const __global__ = this;
  deps.map((d) =>
    typeof d == "number"
      ? runModule(d, { exports: imports })
      : runModule(d[0], { exports: imports }, d[1])
  );
  return func.call(__global__, { imports });
}

分析如上jsScript如下:

  1. GLOBAL['test1#1'] = (function(__initProps__) {...})(convertObjectLiteralToSetOrMap(JSON.parse('{}')));:这行代码界说了一个大局函数 test1#1,该函数的完成是一个自履行的函数。这个自履行的函数接受一个参数 __initProps__,但在这个代码段中,它的值被设置为一个空目标。接下来,这个函数内部运用了一个名为 runCallback 的函数,该函数是从 fair_jsbase.js 中导入的。
  2. runCallback 函数的目的是运转给定的函数 func,而且能够传递一些依靠项。它创立了一个名为 imports 的空目标,而且将当时的大局目标 __global__ 存储在 __global__ 变量中。然后,它循环处理依靠项(在这儿没有供给详细信息),关于每个依靠项,它可能会运转模块并将其输出存储在 imports 中。
  3. runCallback 函数的最终,它经过调用 func.call(__global__, { imports }) 来运转传入的函数 func,而且传递了一个包含 imports 目标的参数。
  4. 在这个特定的代码片段中,传给 runCallback 的函数是一个大函数,它界说了一个名为 _Test5NetPluginState 的结构函数和一些办法。这个结构函数似乎用于创立一个目标,该目标能够履行一些网络恳求和操作。

总结:这段 JavaScript 代码创立了一个大局函数 test1#1,这个函数的完成是一个自履行的函数,它运用 runCallback 函数来处理依靠项并运转给定的函数。在这个特定的示例中,给定的函数界说了一个结构函数 _Test5NetPluginState 和一些办法,用于履行网络恳求等操作。

大局根底通用js

{
  "coreJs": {
    "fair_core": "packages/fair/assets/fair_core/fair_core.js",
    "fair_jsbase": "packages/fair/assets/fair_core/fair_jsbase.js",
    "fair_common_plugin": "packages/fair/assets/fair_core/fair_common_plugin.js"
  }
}

其他可能增加的通用插件js:

{
  "plugin": {
  }
}
fair_image_picker.js
fair_log_plugin.js
fair_navigator_plugin.js
fair_net_plugin.js
fair_permission.js
fair_toast_plugin.js
fair_url_launcher_plugin.js

大局js文件是在FairApp即FairWidget顶层Widget中,FairApp初始化静态办法runApplication中调用:

Runtime().loadCoreJs(package: package, jsPlugins: jsPlugins, baseJsSources: baseJsSources).then((value) => runApp(app));
  • fair_home.json
  • fair_basic_config.json
  • 以及FairApp 的runApplication办法的jsPlugins参数
  • baseJsSources 参数

这4个地方界说JS文件,文件读取为字符串拼装成一个大jsSource内容,一切根底的大局js办法都在这儿加载了:


map[FairMessage.PATH] = baseJsSource + ' ; ' + pluginJsSource;
map[FairMessage.PAGE_NAME] = 'loadCoreJs';
return _channel!.loadJS(jsonEncode(map), null);

调用channel:_methodChannel!.invokeMethod('loadMainJs', args);

对应的native履行为:

// 异步注入到JSContext里
[[FairJSBridge sharedInstance] evaluateScriptWithJSScriptAsync:JSScript callback:callback];
JSValue *jsValue = [self.context evaluateScript:jsScript];

FairWidget展现时候:注入页面js

在FairWidget state的didChangeDependencies 中调用:

_mFairApp.runtime.addScript(state2key, resolveJS, widget.data):

var map = <dynamic, dynamic>{};
map[FairMessage.PATH] = scriptSource;
map[FairMessage.PAGE_NAME] = pageName;
return _channel!.loadJS(jsonEncode(map), null);

对应跳转到Native履行注入JS:

- (JSValue *)evaluateScript:(NSString *)jsScript callback:(FairCallback)callback
{
	JSValue *jsValue = [self.context evaluateScript:jsScript]; //GLOBAL['test1#1']=(函数)(json目标)
	if (callback) {
		callback(jsValue, nil);
	}
	return jsValue;
}

FairWidget页面封闭时候开释 JS:

在 FairState的dispose办法中开释资源:

runtime.release(key);

调用release:

  void dispose() {
    _fairApp?.unregister(this);
    delegate.dispose();
    super.dispose();
  }

_fairApp?.unregister(this);中调用 runtime.release(key);


    void unregister(FairState state) {
    var key = state.state2key;
    _mFairHandler.unregister(state);
    log('unregister state: $key');
    bindData.remove(key)?.clear();
    runtime.release(key);
  }

runtime.release(key);完成为:

     @override
  void release(String? pageName) {
    var map = <dynamic, dynamic>{};
    map[FairMessage.FUNC_NAME] = FairMessage.RELEASE_JS; //releaseJS
    var msg = FairMessage(pageName, FairMessage.METHOD, map);
    _channel?.release(jsonEncode(msg.from()), null);
  }

_channel?.release(jsonEncode(msg.from()), null); 完成为:

  Future<dynamic> release(String args, VoidMsgCallback? callback) {
    return _methodChannel!.invokeMethod('releaseMainJs', args);
  }

调用delegate.dispose

class FairDelegate extends RuntimeFairDelegate 
RuntimeFairDelegate{ 
  void dispose() {
    runtime?.invokeMethod(pageName, 'onUnload', null);
  }
}

对应的channel通道交互为:

  Future<String> invokeMethod(String pageName, String funcName, List<dynamic>? parameters) async {
    var map = <dynamic, dynamic>{};
    map[FairMessage.FUNC_NAME] = funcName;
    map[FairMessage.ARGS] = parameters;
    var msg = FairMessage(pageName, FairMessage.METHOD, map);
    var from = msg.from();
    var reply = _channel!.sendCommonMessage(jsonEncode(from));
    return await reply ?? '';
  }

即 有FairState 的 dispose 中的调用:

  void dispose() {
    _fairApp?.unregister(this); // 履行 releaseJS :js_core.js中界说的办法,GLOBAL大局目标是在这个文件中界说的
    delegate.dispose();//履行 onUnloadjs办法(onUnload是  FairPatch注解的Fair动态界面中完成的onLoad,经过fair的编译器dart2js处理的onLoad办法) 
    super.dispose();
  }

分别触发两个js 办法经过channel调用到native:

办法名:invokeJSFunc

办法参数分别为:

{"pageName":"page1#2","type":"method","args":{"funcName":"releaseJS"}}

{"pageName":"page1#2","type":"method","args":{"funcName":"onUnload","args":null}}

fair_core.js完成为:


let GLOBAL = {}; //GLOBAL 是一个大局目标
function invokeJSFunc(parameter) {
    if (parameter === null) {
        return null;
    }
    let map = JSON.parse(parameter);
    if ('method' === map['type']) {
        return _invokeMethod(map);
    } else if ('variable' === map['type']) {
        return _invokeVariable(map);
    }
    return null;
}

一切的js办法都是走invokeJSFunc

function _invokeMethod(par) {
    let pageName = par['pageName'];
    let funcName = par['args']['funcName'];
    let args = par['args']['args'];
    if ('getAllJSBindData' === funcName) {
        return getAllJSBindData(par);
    }
    if ('releaseJS' === funcName) {
        return _release(par);
    }
    let mClass = GLOBAL[pageName];
    let func = mClass[funcName];
    let methodResult;
    if (isNull(func)) {
        methodResult = '';
    } else {
        methodResult = func.apply(mClass, args);
    }
    let result = {
        pageName: pageName,
        result: {
            result: methodResult
        }
    };
    return JSON.stringify(result);
}

假如参数中的 funcName是releaseJS 则调用_release办法:

function _release(par) {
    let pageName = par['pageName'];
    GLOBAL[pageName] = null;
    return null;
}

对应的native完成:

  FairWeakSelf(weakSelf);
    [self.flutterMethodChannel setMethodCallHandler:^(FlutterMethodCall *call, FlutterReply callback) {        FairStrongObject(strongSelf, weakSelf);                // 获取字典数据        NSString *method = call.method;        FairDartModel *model = [strongSelf obtainModelWithMessage:call.arguments];
        // 加载js
        if ([method isEqualToString:@"loadMainJs"]) {
          //省掉
        }
        // 开释js
        else if ([method isEqualToString:@"releaseMainJs"]) {
            if ([strongSelf.delegate respondsToSelector:@selector(disposePage:)] && FAIR_IS_NOT_EMPTY_STRING(model.pageName)) {
                [strongSelf.delegate disposePage:model.pageName];
            }
        }
    }];

JS注入与开释

经过以上流程分析,由于在调用_methodChannel!.invokeMethod('releaseMainJs', args); iOS端native的完成没有对args参数进行解析,只是调用 [strongSelf.delegate disposePage:model.pageName];即:

- (void)disposePage:(NSString *)pageName
{
    if (FAIR_IS_NOT_EMPTY_STRING(pageName)) {
        self.context[pageName] = nil;
    }
}

经过Safari对JSContext的调试,增加监视表达式:

58fair 动态化界面逻辑JavaScriptCore js对象的创建与释放

经过多次进入几个页面,在fair动态界面push fair动态界面,然后回到主页, 检查GLOBAL大局目标值:

58fair 动态化界面逻辑JavaScriptCore js对象的创建与释放

/fair/lib/src/runtime/runtime_fair_delegate.dart

所以能够修正一下FairDartBridge.m代码 setDartListener 的完成:

      // 用来监听dart对native的调用
- (void)setDartListener {
    // 注入js
    FairWeakSelf(weakSelf);
    [self.flutterMethodChannel setMethodCallHandler:^(FlutterMethodCall *call, FlutterReply callback) {        FairStrongObject(strongSelf, weakSelf);                // 获取字典数据        NSString *method = call.method;        FairDartModel *model = [strongSelf obtainModelWithMessage:call.arguments];
        // 加载js
        if ([method isEqualToString:@"loadMainJs"]) {
            //省掉
        }
        // 开释js
        else if ([method isEqualToString:@"releaseMainJs"]) {
            if ([strongSelf.delegate respondsToSelector:@selector(disposePage:)] && FAIR_IS_NOT_EMPTY_STRING(model.pageName)) {
                [strongSelf.delegate disposePage:model.pageName];
            }
            //新增加,解析参数履行js办法:{"pageName":"page1#2","type":"method","args":{"funcName":"releaseJS"}}
            if (strongSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(executeJSFunctionAsync:params:callback:)]) {
                NSString *message = call.arguments;
                NSArray *params = message && [message isKindOfClass:[NSString class]] ? @[message] : @[];
                [strongSelf.delegate executeJSFunctionAsync:FairExecuteJSFunction params:params callback:^(id result, NSError *error) {                    FairLog(@"%@", result);                    JSValue *value = result;                    if (value && [value isKindOfClass:[JSValue class]]) {
                        NSString *str = value.toString;
                        FairLog(@"%@", str);
                        if (![str isEqualToString:@"undefined"] && FAIR_IS_NOT_EMPTY_STRING(str)) {
                            callback(str);
                        }
                    } 
                }];
            }
        }
    }];
}

但是这样会导致后续的onUnload办法现已被 GLOBAL 清空了无法调用少了逻辑。所以naitve不变,在dart侧做修正:

修正/fair/lib/src/runtime/runtime_fair_delegate.dart 代码完成:

void dispose() {
  runtime?.invokeMethod(pageName, 'onUnload', null);//先调用页面js文件的onUnLoad假如有。
  runtime?.invokeMethod(pageName, 'releaseJS', null);//再调用fair_core.js的_release 办法
}

再次运转项目:

58fair 动态化界面逻辑JavaScriptCore js对象的创建与释放

self.context[pageName] = nil // pageName:"test1#1"

GLOBAL[pageName] = null;//GLOBAL目标铲除

[self.context evaluateScript:jsScript] //jsScript:GLOBAL['test1#1'] = (function(__initProps__) {...})(convertObjectLiteralToSetOrMap(JSON.parse('{}')));

综上,经过这样的成对装备,就完成了Javascript GLOBAL 目标的创立与开释,避免了内存的走漏。