美文网首页Flutter开发
2023-07-20 58fair逻辑动态化原理和运行流程

2023-07-20 58fair逻辑动态化原理和运行流程

作者: 我是小胡胡123 | 来源:发表于2023-07-19 16:46 被阅读0次
  • 1,编写widget
  • 2,转化成json文件和js 文件
  • 3,json 文件和js 文件加载,转化成widget,功能逻辑在js中
  • 4,点击页面方法,方法是如何执行,并且如何刷新页面的

1、编写widget

fair 的接入过程请看文档:https://fair.58.com/zh...

那么这里写了一个简单的页面widget:
代码内容下:

import 'package:fair/fair.dart';
import 'package:flutter/material.dart';

@FairPatch()
class JsonFileExplain extends StatefulWidget {
  const JsonFileExplain({Key? key}) : super(key: key);

  @override
  _JsonFileExplainState createState() => _JsonFileExplainState();
}

class _JsonFileExplainState extends State<JsonFileExplain> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  Widget _buildTitle() {
    return Text('title');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: _buildTitle(),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '${_counter}',
              // '123',
              style: TextStyle(
                  fontSize: 40, color: Color(0xffeb4237), wordSpacing: 0),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

这个widget页面,添加了 @FairPatch() 就将被生成json文件和js文件。

2、转化成json文件和js 文件

通过命令:

 flutter packages pub run build_runner build --delete-conflicting-outputs

生成的js 文件内容下:
lib_test_fair_json_file_read.fair.js:

GLOBAL['#FairKey#'] = (function(__initProps__) {
    const __global__ = this;
    return runCallback(function(__mod__) {
        with(__mod__.imports) {
            function _JsonFileExplainState() {
                const inner = _JsonFileExplainState.__inner__;
                if (this == __global__) {
                    return new _JsonFileExplainState({
                        __args__: arguments
                    });
                } else {
                    const args = arguments.length > 0 ? arguments[0].__args__ || arguments: [];
                    inner.apply(this, args);
                    _JsonFileExplainState.prototype.ctor.apply(this, args);
                    return this;
                }
            }
            _JsonFileExplainState.__inner__ = function inner() {
                this._counter = 0;
            };
            _JsonFileExplainState.prototype = {
                _incrementCounter: function _incrementCounter() {
                    const __thiz__ = this;
                    with(__thiz__) {
                        setState('#FairKey#',
                        function dummy() {
                            _counter++;
                        });
                    }
                },
            };
            _JsonFileExplainState.prototype.ctor = function() {};;
            return _JsonFileExplainState();
        }
    },
    []);
})(convertObjectLiteralToSetOrMap(JSON.parse('#FairProps#')));

生成的json 文件 如下:

lib_test_fair_test_plugin_widget.fair.json:

{
  "className": "Scaffold",
  "na": {
    "appBar": {
      "className": "AppBar",
      "na": {
        "title": "%(_buildTitle)"
      }
    },
    "body": {
      "className": "Center",
      "na": {
        "child": {
          "className": "Column",
          "na": {
            "mainAxisAlignment": "#(MainAxisAlignment.center)",
            "children": [
              {
                "className": "Text",
                "pa": [
                  "You have pushed the button this many times:"
                ]
              },
              {
                "className": "Text",
                "pa": [
                  "#(${_counter})"
                ],
                "na": {
                  "style": {
                    "className": "TextStyle",
                    "na": {
                      "fontSize": 40,
                      "color": {
                        "className": "Color",
                        "pa": [
                          "0xffeb4237"
                        ]
                      },
                      "wordSpacing": 0
                    }
                  }
                }
              }
            ]
          }
        }
      }
    },
    "floatingActionButton": {
      "className": "FloatingActionButton",
      "na": {
        "onPressed": "@(_incrementCounter)",
        "tooltip": "Increment",
        "child": {
          "className": "Icon",
          "pa": [
            "#(Icons.add)"
          ]
        }
      }
    }
  },
  "methodMap": {
    "_buildTitle": {
      "className": "Text",
      "pa": [
        "title"
      ]
    }
  }
}

从这里我们的页面就转化成了一个json 文件和js文件。json文件对应的是页面布局。js文件对应的是逻辑方法。

3、json 文件和js 文件加载,转化成widget,功能逻辑在js中

使用 FairWidget 加载json文件和js文件。

最后页面渲染效果如下:


Simulator Screen Shot - iPhone 14 Pro - 2023-07-20 at 15.32.30.png

4、点击页面方法,方法是如何执行,并且如何刷新页面的

点击加号,首先会执行dart方法:

 return ([props]) =>
            _functions?['runtimeInvokeMethod']?.call(funcName, props);

这个方法实现为:

    _bindFunctionsMap['runtimeInvokeMethod'] = (funcName, [props]) {
      var arguments;
      if (props != null) {
        arguments = [];
        arguments.add(props);
      }
      return runtime?.invokeMethod(pageName, funcName, arguments);
    };

通过runtime运行时调用invokeMethod
runtime是fair定一个Runtimer类型的对象。所有动态逻辑执行的相关的方法都通过runtime对象进行调用。

class Runtime implements IRuntime {
     MethodChannel getBasicChannel(){
    return _channel!.basicMethodChannel!;
  }

  FairMessageChannel? _channel;
}

FairMessageChannel类型是fair对native通讯通道对象的封装,定义如下:

class FairMessageChannel {
  Pointer<Utf8> Function(Pointer<Utf8>) invokeJSCommonFuncSync = dl
      .lookup<NativeFunction<Pointer<Utf8> Function(Pointer<Utf8>)>>(
          'invokeJSCommonFuncSync')
      .asFunction();

  BasicMessageChannel<String?>? _commonChannel;
  MethodChannel? _methodChannel;
  MethodChannel? basicMethodChannel;
}

所以dart逻辑都会调用 BasicMessageChannel 和 Dart-FFI,MethodChannel三种方式 与native 进行通讯。

回到刚才点击加号,往下面走,执行到这个方法:

runtime?.invokeMethod(pageName, funcName, arguments);

runtime的invokeMethod方法实现为:

  var reply = _channel!.sendCommonMessage(jsonEncode(from));

这个channel通道就调用到native中去了BasicMessageChannel

native channel通道方法定义如下

    // 执行js
    [self.flutterBasicMessageChannel setMessageHandler:^(id message, FlutterReply callback) {
        FairStrongObject(strongSelf, weakSelf);
        
        FairLog(@"%@", message);

        if (strongSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(executeJSFunctionAsync:params:callback:)]) {
            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);
                    }
                }
            }];
        }
    }];

这个 executeJSFunctionAsync 的实现如下:

- (JSValue *)invokeJSFunctionOnJSThread:(NSString *)functionName params:(NSArray *)params callback:(FairCallback)callback
{
    JSValue *jsValue = self.context[functionName];
    
    JSValue *value = [jsValue callWithArguments:params];

    if (callback) {
        callback(value, nil);
    }
    return value;
}

看到这里,我们的functionName打印如下:
Printing description of functionName:
invokeJSFunc

这是一个 js 方法。
回顾一下, 点击加号, dart 调用 runtimeInvokeMethod dart方法,继而通过BasicMessageChannel 调用到native,然后通过native通道方法,使用JSContext调用到js 方法方法名字为 invokeJSFunc,并且参数为

<__NSSingleObjectArrayI 0x6000038a8590>(
{"pageName":"lib_test_fair_json_file_read#0","type":"method","args":{"funcName":"_incrementCounter","args":null}}
)

而这个 js 方法定义,我们从文件fair_core.js中找到了:

/*
 * 用户的基础js,一般情况不需要改动
 */

let 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;
}

function _invokeVariable(par) {
    console.log('_invokeVariable' + JSON.stringify(par));
    let pName = par['pageName'];
    let varMap = par['args'];
    let curPage = GLOBAL[pName];
    let callResult = {
        pageName: pName,
        result: {}
    };
    if (!isNull(varMap) && Object.keys(varMap).length > 0) {
        Object.keys(varMap).forEach(function (varKey) {
            callResult['result'][varKey] = eval('curPage.' + varKey.toString());
        });
        return JSON.stringify(callResult);
    }
    //如果没有传参数,默认返回全部的变量以及结果值
    Object.keys(curPage).forEach(function (key) {
        if (!isFunc(curPage[key])) {
            callResult['result'][key] = eval('curPage.' + key.toString());
        }
    });
    return JSON.stringify(callResult);
}

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);
}

function _getAll(par) {
    let pageName = par['pageName'];
    let mc = GLOBAL[pageName];
    let bind = {}
    if (isNull(mc)) {
        return JSON.stringify(bind);
    }
    let bindFunc = [];
    let bindVariables = {};
    let keys;
    if (!isNull(keys = Object.keys(mc))) {
        for (let i = 0; i < keys.length; i++) {
            let k = keys[i];
            if (!mc.hasOwnProperty(k)) {
                continue;
            }
            if (isFunc(mc[k])) {
                bindFunc.push(k);
            } else {
                bindVariables[k] = mc[k];
            }
        }
    }
    bind['func'] = bindFunc;
    bind['variable'] = bindVariables;
    return bind;
}

//demo 获取所有的变量和绑定的方法
function getAllJSBindData(par) {
    let pageName = par['pageName'];
    let bind = _getAll(par);
    let result = {
        pageName: pageName,
        result: {
            result: bind
        }

    };
    return JSON.stringify(result);
}


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


function isFunc(name) {
    return typeof name === "function";
}

function isNull(prop) {
    return prop === null || 'undefined' === prop
        || 'undefined' === typeof prop
        || undefined === typeof prop
        || 'null' === prop;
}

function setState(pageName, obj) {
    console.log('JS:setState()_before' + pageName + '-' + obj);
    let p = {};
    p['funcName'] = 'setState';
    p['pageName'] = pageName;
    // console.log('JS:setState(states)'+JSON.stringify(Object.getOwnPropertySymbols(obj)));
    obj();
    p['args'] = null;
    let map = JSON.stringify(p);
    console.log('JS:setState()' + map);
    invokeFlutterCommonChannel(map);
}
function mapOrSetToObject(arg) {

    if (Object.prototype.toString.call(arg) === '[object Map]') {
        let obj1 = {}
        for (let [k, v] of arg) {
            obj1[k] = mapOrSetToObject(v);
        }
        return obj1;
    }

    if (Object.prototype.toString.call(arg) === '[object Array]') {
        let obj2 = [];
        for (let k of arg) {
            obj2.push(mapOrSetToObject(k));
        }
        return obj2;
    }

    if (Object.prototype.toString.call(arg) === '[object Object]') {
        let keys = Object.getOwnPropertyNames(arg);
        let obj3 = {};
        for (let key of keys) {
            let value = arg[key];
            obj3[key] = mapOrSetToObject(value);
        }
        return obj3;
    }

    return arg;
}

const invokeFlutterCommonChannel = (invokeData, callback) => {
    console.log("invokeData" + invokeData)
    jsInvokeFlutterChannel(invokeData, (resultStr) => {
        console.log('resultStr' + resultStr);
        if (callback) {
            callback(resultStr);
        }
    });
};
 

这段js 方法的流程如下:

js方法invokeJSFunc,调用_invokeMethod , 然后从参数 funcName 为 _incrementCounter 的js 方法, 这个js 方法就是一开始我们页面被flutter build_runner生成转化的js 文件中。
我们继续回顾一下 js 文件中的 _incrementCounter 方法:

      _incrementCounter: function _incrementCounter() {
                    const __thiz__ = this;
                    with(__thiz__) {
                        setState('#FairKey#',
                        function dummy() {
                            _counter++;
                        });
                    }
                },

点击加号,执行了这段js 逻辑,继续调用setState方法
这个setState方法同样也是在 fair_core.js 中定义了的:

function setState(pageName, obj) {
    console.log('JS:setState()_before' + pageName + '-' + obj);
    let p = {};
    p['funcName'] = 'setState';
    p['pageName'] = pageName;
    // console.log('JS:setState(states)'+JSON.stringify(Object.getOwnPropertySymbols(obj)));
    obj();
    p['args'] = null;
    let map = JSON.stringify(p);
    console.log('JS:setState()' + map);
    invokeFlutterCommonChannel(map);
}

这里我们有需要调用js 的方法 invokeFlutterCommonChannel 。那么这个js方法在哪里定义了呢:
继续看,还是在jsCore.js文件中:

const invokeFlutterCommonChannel = (invokeData, callback) => {
    console.log("invokeData" + invokeData)
    jsInvokeFlutterChannel(invokeData, (resultStr) => {
        console.log('resultStr' + resultStr);
        if (callback) {
            callback(resultStr);
        }
    });
};

从而最终,js方法都会走向 jsInvokeFlutterChannel 这个js方法。
那么这个方法定义是在native中,看代码:

NSString * const FairExecuteDartFunctionAsync = @"jsInvokeFlutterChannel";
      // JS 异步调用 Dart
        _context[FairExecuteDartFunctionAsync] = ^(id receiver, JSValue *callback) {
            FairStrongObject(strongSelf, weakSelf)
            
            NSString *data = [strongSelf convertStringWithData:receiver];
            if ([strongSelf.delegate respondsToSelector:@selector(FairExecuteDartFunctionAsync:callback:)]) {
                [strongSelf.delegate FairExecuteDartFunctionAsync:data callback:callback];
            }
        };
        

再看一下这个FairExecuteDartFunctionAsync:callback:的实现:

- (void)FairExecuteDartFunctionAsync:(NSString *)data callback:(JSValue *)callback
{
    [[FairDartBridge sharedInstance] sendMessageToDart:data callback:^(id result, NSError *error) {
        [[FairJSBridge sharedInstance] invokeJSFunction:callback param:result];
    }];
}

这里做了2件事:
第一件事是调用sendMessageToDart:callback:
第二件事是调用invokeJSFunction:param:

第一件事是

- (void)sendMessageToDart:(NSString *)message callback:(FairCallback)callback {
    [self.flutterBasicMessageChannel sendMessage:message reply:^(id reply) {
        if (callback && FAIR_IS_NOT_EMPTY_STRING(reply)) {
            callback(reply, nil);
        }
    }];
}

这是调用 flutterBasicMessageChannel 通道,调用dart 代码:

dart flutterBasicMessageChannel 通道方法定义如下:

    _commonChannel!.setMessageHandler((String? message) async {
      print('来自native端的消息:$message');
      //js 异步调用dart中的相关方法
      var data = json.decode(message??'');
      var funcName = data['funcName']?.toString();

      if (funcName == 'invokePlugin') {
        var p = await FairPluginDispatcher.dispatch(message);
        return p;
      }

      _callback?.call(message);
      return 'reply from dart';
    });

_callback方法的实现为:

//接收native发送过来的消息,实际上是js发送的消息,通过native端透传过来
    _runtime.getChannel().setMessageHandler((String? message) {
      var data = json.decode(message ?? '');
      var funcName = data['funcName']?.toString();
      var pageName = data['pageName'];
      var args = data['args']??{};

      if (funcName == null || funcName.isEmpty) {
        return '';
      }

      //当用户调用setState的时候相当于刷新了数据,通知刷新页更新
      if (funcName == 'setState') {
        _dispatchMessage(pageName, jsonEncode(args));
        return '';
      }
    });




    _commonChannel!.setMessageHandler((String? message) async {
      print('来自native端的消息:$message');
      //js 异步调用dart中的相关方法
      var data = json.decode(message??'');
      var funcName = data['funcName']?.toString();

      if (funcName == 'invokePlugin') {
        var p = await FairPluginDispatcher.dispatch(message);
        return p;
      }

      _callback?.call(message);
      return 'reply from dart';
    });

_dispatchMessage方法实现为:

  String _dispatchMessage(String pageName, String message) {
    pageHistories[pageName]?.call(message);
    return 'Reply from Dart';
  }

pageHistories定义如下:

  void call(String t) {
    var params={};
    try {
      params= jsonDecode(t);
    } catch (e) {
      print(e);
    }
    delegate.notifyValue(params);
  }


  //获取js端的数据,刷新指定数据
  void notifyValue(Map values) {
 
    setState(() {});
  }


  void setState(VoidCallback fn) {
    if (_state == null || !_state!.mounted) return;
 
    _state?.setState(fn);
    _state?._reload();
  }


  void _reload() {
    if (mounted) {
      var name = state2key;
      var path = widget.path ?? _fairApp!.pathOfBundle(widget.name ?? '');
      bundleType =
          widget.path != null && widget.path?.startsWith('http') == true
              ? 'Http'
              : 'Asset';

      parse(context, page: name, url: path, data: widget.data ?? {})
          .then((value) {
        if (mounted && value != null) {
          setState(() => _child = value);
        }
      });
    }
  }

这样就重新加载了一次页面,完成了页面更新。

回到上面第二件事情callback的调用invokeJSFunction:param:
invokeJSFunction:param:方法为:

- (JSValue *)invokeJSFunctionOnJSThread:(JSValue *)function params:(NSArray *)params
{
    JSValue *value = [function callWithArguments:params];
    return value;
}

这个callback参数为:

      _context[@"jsInvokeFlutterChannel"] = ^(id receiver, JSValue *callback) {
            FairStrongObject(strongSelf, weakSelf)
            
            NSString *data = [strongSelf convertStringWithData:receiver];
            if ([strongSelf.delegate respondsToSelector:@selector(FairExecuteDartFunctionAsync:callback:)]) {
                [strongSelf.delegate FairExecuteDartFunctionAsync:data callback:callback];
            }
        };

callback参数是一个JSValue对象,是一个js对象,js的函数:

  jsInvokeFlutterChannel(invokeData, (resultStr) => {
        console.log('resultStr' + resultStr);
        if (callback) {
            callback(resultStr);
        }
    });

那么这里就是结果回调js。

通过流程分析,回顾一下整个流程为:
点击加号,dart代码执行 runtimeInvokeMethod dart方法,继而通过BasicMessageChannel 调用到native,然后通过native通道方法,使用JSContext调用到js 方法方法名字为 invokeJSFunc,并且参数为具体的执行函数名字_incrementCounter,调用js方法后,继续调用 jsInvokeFlutterChannel,回到native,native 通过flutterBasicMessageChannel通道调用dart,执行dart,dart通过BasicMessageChannel定义了setMessageHandler方法,从而调用 _dispatchMessage 方法 通过不同funcName对应setstate 刷新界面。

相关文章

  • iOS逆向-动态调试

    动态调试 将程序运行起来,通过断点,打印等方式,查看参数,返回值,函数调用流程等 Xcode动态调试原理 xcod...

  • 动态调试

    动态调试简介 将程序运行起来,通过下断点,打印等方式,查看参数,返回值,函数调用流程等。 Xcode的动态调试原理...

  • 动态调试

    Xcode动态调试原理 动态调试就是将程序运行起来,通过下断点、打印等方式查看参数、返回值、函数调用流程等。 Xc...

  • Android插件化

    动态加载技术 原理:在应用程序运行时,动态加载一些程序中原本不存在的可执行文件并运行这些文件里的代码逻辑。可执行文...

  • 16_动态调试

    将程序运行起来,通过打断点,打印等方式,查看参数、返回值、函数调用等流程等。 一、xcode的动态调试原理 首先,...

  • JAVASCRIPT DOM事件

    js事件原理 •在某个业务场景下的动作触发运行的逻辑流程 •事件源:触发当前动作的源头组件(元素),可以是当前页面...

  • 动态代理-Cglib实现原理

    动态代理-Cglib实现原理 Cglib提供了一种运行时动态增强类的功能。基于ASM在运行时动态创建class,暴...

  • PHP扩展

    php扩展运行原理 ①php运行原理zend引擎初始化 ->extensions->sapi->上层应用扩展运行原...

  • Android插件化原理

    1、前言 这篇文章来讲一下Android插件化的原理和大概的一个运行流程,最后将一个demo呈现出来。 2、分析 ...

  • mybatis

    实现原理 JDK动态代理,jdbc 启动流程 读取mybatis-config.xmlSqlSessionFact...

网友评论

    本文标题:2023-07-20 58fair逻辑动态化原理和运行流程

    本文链接:https://www.haomeiwen.com/subject/vrtaudtx.html