58fair FairPlugin插件运行流程和工作机制

235 阅读4分钟
  • 页面js逻辑:逻辑lib_hotel_listview.fair.js 业务逻辑调用FairNet.requestData插件中的js方法
  • js插件:通用插件fair_net_plugin.js FairNet类requestData方法的实现
  • fair基础通用js文件: fair_core.js 定义通用js方法invokeFlutterCommonChannel
  • native js:方法jsInvokeFlutterChannelSync定义 _context[FairExecuteDartFunctionSync]
  • dart 插件功能内置

本文将从这5个部分记录一下FairPlugin的工作过程

1.页面JS文件,页面中调用FairNet.requestData

页面逻辑js文件加载的逻辑: 在页面生成的js逻辑文件中,会调用js方法,这个js方法的实现将会在插件js文件中定义,这里只是对 FairNet.requestData进行调用,执行传递请求参数:

页面js逻辑文件是随着页面FairWidget的渲染而每次都会加载到内存的,通过channel调用loadMainJs方法加载native得JSContext js环境中。

并且会为每一个页面动态实时的生成一个唯一ID state2key

state2key pageName和自增id组成,每一个逻辑js文件是fairwiget创建时候加载js文件到内存的 页面加载时 #FairKey# 将会被动态替换为state2key 替换代码:

    scriptSource = scriptSource.replaceFirst(RegExp(r'#FairProps#'), fairProps);
    scriptSource = scriptSource.replaceAll(RegExp(r'#FairKey#'), pageName);
    var map = <dynamic, dynamic>{};
    map[FairMessage.PATH] = scriptSource;
    map[FairMessage.PAGE_NAME] = pageName;

页面逻辑JS文件中插件方法调用:

dart代码:

/// 生命周期回调,函数名不可修改
void onLoad() {
  requestData();
}

/// 生命周期回调,函数名不可修改
void onUnload() {}

void requestData() {
  _page++;
  FairNet.requestData(
      method: 'GET',
      url:
          'https://wos2.58cdn.com.cn/DeFazYxWvDti/frsupload/3be6c61070d3b48c8165af5d18464c0e_hotel_list_data.json',
      data: {
        'page': _page,
        'pageName': '#FairKey#',
      },
      success: (resp) {
        if (resp == null) {
          return;
        }
        var data = resp['data'];
        data.forEach((item) {
          var dataItem = HotelModel();
          try {
            dataItem.imagePath = item.imagePath;
            dataItem.titleTxt = item.titleTxt;
            dataItem.subTxt = item.subTxt;
            dataItem.dist = item.dist + ' km';
            dataItem.reviews = item.reviews + ' reviews';
            dataItem.perNight = item.perNight + '';
          } catch (e) {
            dataItem.imagePath = item['imagePath'];
            dataItem.titleTxt = item['titleTxt'];
            dataItem.subTxt = item['subTxt'];
            dataItem.dist = item['dist'] + ' km';
            dataItem.reviews = item['reviews'] + ' reviews';
            dataItem.perNight = item['perNight'] + '';
          }
          _listData.add(dataItem);
        });
        setState(() {});
      });
}

dart代码转化成js:

 _HotelListViewState.prototype = {
    onLoad: function onLoad() {
        const __thiz__ = this;
        with(__thiz__) {
            requestData();
        }
    },
    onUnload: function onUnload() {
        const __thiz__ = this;
        with(__thiz__) {}
    },
    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']: '#FairKey#',
                }),
                success: function dummy(resp) {
                    if (resp == null) {
                        return null;
                    }
                    let data = resp.__op_idx__('data');
                    data.forEach(function dummy(item) {
                        let dataItem = HotelModel();
                        try {
                            dataItem.imagePath = item.imagePath;
                            dataItem.titleTxt = item.titleTxt;
                            dataItem.subTxt = item.subTxt;
                            dataItem.dist = item.dist + ' km';
                            dataItem.reviews = item.reviews + ' reviews';
                            dataItem.perNight = item.perNight + '';
                        } catch (e) {
                            dataItem.imagePath = item.__op_idx__('imagePath');
                            dataItem.titleTxt = item.__op_idx__('titleTxt');
                            dataItem.subTxt = item.__op_idx__('subTxt');
                            dataItem.dist = item.__op_idx__('dist') + ' km';
                            dataItem.reviews = item.__op_idx__('reviews') + ' reviews';
                            dataItem.perNight = item.__op_idx__('perNight') + '';
                        }
                        _listData.add(dataItem);
                    });
                    setState('#FairKey#', function dummy() {});
                }
            });
        }
    },
 

在onLoad函数中调用 requestData,FairNet.requestData的实现在插件里

页面逻辑js标识

state2key算法:

    state2key = GlobalState.id(widget.name);
  static String id(String? prefix) {
    return '$prefix#${GlobalState._counter++}';
  }

2.插件JS文件在根级FairApp创建时候被加载

插件JS文件: 插件的js基础js文件时在app启动时候通过 Runtime().loadCoreJs(package: package, jsPlugins: jsPlugins, baseJsSources: baseJsSources).then((value) => runApp(app)); 方法加载的。这里没有#FairKey#模式字符串的替换过程 map[FairMessage.PATH] = baseJsSource + ' ; ' + pluginJsSource; map[FairMessage.PAGE_NAME] = 'loadCoreJs';

3.js插件fair_net_plugin.js 实现FairNet.requestData

js插件内通过 invokeFlutterCommonChannel 函数,invokeFlutterCommonChannel方法是在fair_core.js中定义的,再又通jsInvokeFlutterChannel方法去调用dart端的实现,jsInvokeFlutterChannel方法是在native端直接注册的。


let FairNetCallBack = {}; //需要注意的是这个名字是全局变量,不同的插件需要定义为不同的对象名
 let callBackId = 0; //不同插件变量名字不能一样
 static requestData(req) {
     let respMap = {};
     let id = 'FairNet$' + (callBackId++); // ID规则 callBackId全局变量自增
     // 设置回调
     let reqFunc = {}; //回调block 存储3个回调block 
     if (req.complete) {
         reqFunc['complete'] = req.complete;
     }
     if (req.success) {
         reqFunc['success'] = req.success;
     }
     if (req.failure) {
         reqFunc['failure'] = req.failure;
     }
     FairNetCallBack[id] = reqFunc; //FairNetCallBack 全局变量将结果回调函数存储,通过ID获取callback,这样将结果回调
     // 处理参数
     let method = '';
     if (req.method) {
         method = req.method;
     }
     let url = '';
     if (req.url) {
         url = req.url;
     }
     let data = {};
     if (req.data) {
         data = mapOrSetToObject(req.data); //业务参数 
     }
     let reqMap = {
         pageName: '#FairKey#', //这个PageName其实也没啥用
         funcName: 'invokePlugin',
         'className': 'FairNet#requestData',
         args: {
             callId: id,
             method: method,
             url: url,
             data: data //这里面的 ['pageName']: '#FairKey#',就是state2key,即'$pageName#${GlobalState._counter++}';
         }
     };
//将会被转化成:
//       {
//     "pageName": "#FairKey#",
//     "funcName": "invokePlugin",
//     "className": "FairNet#requestData",
//     "args":
//     {
//         "callId": "FairNet$0",
//         "method": "GET",
//         "url": "https://wos2.58cdn.com.cn/DeFazYxWvDti/frsupload/3be6c61070d3b48c8165af5d18464c0e_hotel_list_data.json",
//         "data":
//         {
//             "page": 1,
//             "pageName": "HotelListView#0"
//         }
//     }
// }
     invokeFlutterCommonChannel(JSON.stringify(reqMap), (resultStr) =>{//调用通用的方法invokeFlutterCommonChannel这个方法在fair_core.js中定义
         let responseMap = JSON.parse(resultStr);
         let data = responseMap['data'];
         responseMap['data'] = data.data;
         let id = responseMap['callId'];
         //处理需要返回的结果值
         let callback = FairNetCallBack[id]; //从全局变量中获取回调callback,将结果回调
         if (callback == null) {
             return
         }
         let complete = callback['complete'];
         let failure = callback['failure'];
         let success = callback['success'];
         if (responseMap['statusCode'] === 200) {
             if (complete != null) {
                 complete();
             }
             if (success != null) {
                 success(convertObjectLiteralToSetOrMap(responseMap));
             }
         } else {
             if (complete != null) {
                 complete();
             }
             if (failure != null) {
                 failure(responseMap['statusMessage']);
             }
         }
     })
 }

4.通用js fair_core.js中定义invokeFlutterCommonChannel

const invokeFlutterCommonChannel = (invokeData, callback) => {
    console.log("invokeData" + invokeData)
    jsInvokeFlutterChannel(invokeData, (resultStr) => { //调用native定义的jsInvokeFlutterChannel方法
        console.log('resultStr' + resultStr);
        if (callback) {
            callback(resultStr);
        }
    });
};

5.native端定义jsInvokeFlutterChannel

//jsContext-》jsInvokeFlutterChannel
NSString * const FairExecuteDartFunctionAsync = @"jsInvokeFlutterChannel";
_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
- (void)FairExecuteDartFunctionAsync:(NSString *)data callback:(JSValue *)callback
{
    [[FairDartBridge sharedInstance] sendMessageToDart:data callback:^(id result, NSError *error) {
        [[FairJSBridge sharedInstance] invokeJSFunction:callback param:result];
    }];
}

//sendMessageToDart
- (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);        }    }];
}

6.dart 插件内置逻辑实现,fair_net_plugin.js插件的落地调用实际会走到dart,由dart实现网络请求

_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); //_callback实现在下方
   return 'reply from dart';
});

//_callback实现:
_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 '';
   }

   // //js 异步调用dart中的相关方法
   // if (funcName == 'invokePlugin') {
   //   FairPluginDispatcher.dispatch(jsonEncode(args))
   //       .then((value) => _runtime.getChannel().sendCommonMessage(value));
   //   return '';
   // }
   return '';
});

 

7.fair_net_plugin.dart实现

  static Future<dynamic> _request(dynamic map) async {


    if (map == null) {
      return;
    }
    var req;
    bool isDart;
    if (map is Map) {
      isDart = true;
      req = map;
    } else {
      isDart = false;
      req = jsonDecode(map);
    }
    var pageName = req['pageName'];
    var args = req['args'];
    if (isDart) {
      args = req;
    }

    var url = args['url'];
    var callId = args['callId'];
    var successCallback = args['success'];
    var failureCallback = args['failure'];
    var completeCallback = args['complete'];
    Map<String, dynamic> reqData = args['data'];//业务参数
    var method = args['method'];
    Response<dynamic>? response;

    if (method == null) {
      return Future.value();
    }

    switch (method) {
      case 'GET':
        response = await _get(url, queryParameters: reqData);
        break;
      case 'POST':
        response = await _post(url, queryParameters: reqData);
        break;
    }

    var statusCode = response?.statusCode;
    var data = response?.data;
    var statusMessage = response?.statusMessage;

    /// 需要判断发起方的请求是dart端还是js端
    if (isDart) {
      /// 实际处理结合自身app的业务逻辑场景
      if (200 == statusCode) {
        successCallback?.call(data);
        completeCallback?.call();
      } else {
        failureCallback?.call(statusMessage);
        completeCallback?.call();
      }
      return Future.value();
    } else {

        // let reqMap = {
        //     pageName: '#FairKey#',
        //     funcName: 'invokePlugin',
        //     'className': 'FairNet#requestData',
        //     args: {
        //         callId: id,
        //         method: method,
        //         url: url,
        //         data: data
        //     }
        // };

                //将会被转化成: map:
        //       {
        //     "pageName": "#FairKey#",
        //     "funcName": "invokePlugin",
        //     "className": "FairNet#requestData",
        //     "args":
        //     {
        //         "callId": "FairNet$0",
        //         "method": "GET",
        //         "url": "https://wos2.58cdn.com.cn/DeFazYxWvDti/frsupload/3be6c61070d3b48c8165af5d18464c0e_hotel_list_data.json",
        //         "data":
        //         {
        //             "page": 1,
        //             "pageName": "HotelListView#0"
        //         }
        //     }
        // }
      var resp = {
        'callId': callId,// 透传回调,用于调用发起方通过id寻找回调callback函数
        'pageName': pageName, //这个pageName其实就只是pageName,不是state2key
        'statusCode': response?.statusCode,
        'data': response?.data,//
        'statusMessage': response?.statusMessage,
      };


      return Future.value(jsonEncode(resp));
    }
  }

8.native FairExecuteDartFunctionAsync方法实现

native dart数据回传给js

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

总结

本文主要通过从FairNet插件的使用实例Fair的ListView模版,研究插件的运行流程和工作机制。官方的用法步骤,点击查看

  • 通用js,通用的js方法调用,通过funcName,arg,pageName解析,然后进行转发。所有动态逻辑都是同一个方法名字invokeFlutterCommonChannel。
  • 插件js,页面js生成逻辑中,没有识别的js方法将需要通过插件实现,这是一个app全局级别的加载,在FairApp启动时加载到naitve JavascriptCore环境中。
  • 页面js,将页面转化为js方法,不会识别的js方法,将在js插件中查找,通过调用js中的插件方法,将参数从js逻辑中传递,调用js-native-dart-js的执行流程。另外一点就是每一个页面js都会随着页面加载动态加载,随着页面释放而释放。
  • native通用js函数名,js调用dart,dart将结果返回
  • dart实现内置逻辑使用dart实现功能