Fair 逻辑动态化,是对一期布局动态化的加强。为了实现逻辑动态化,咱们过后思考了多种计划,计划次要集中在这三个方面,一种是对google提供的JIT进行裁切,第二种是自定义解析引擎,第三种借助js的能力。

上面次要讲一下几方面:

  • 架构的标准化
  • 通信协议的实现
  • js文件的加载与开释
  • 数据的绑定
  • 音讯的散发
  • 第三方扩大(用户依据须要扩大更精彩的性能)

一、架构的标准化

当咱们生成了布局文件和逻辑文件,接下来要做的是如何建设他们之间的分割,确切的说是如何建设两者之间的通信。为了不便资源的管制与调配,fair是对每一个逻辑js通过惟一的key确认,而后通过key-value的模式将逻辑文件长期贮存在一个全局的汇合中。因这个key在生成js的时候是不确定的,所以咱们对key对立定义名称为#FairKey# ,在js发送到native侧之前,由dart侧全局替换该值为具体的key。这个key十分重要,在音讯通信的时候,须要通过key获取具体的通信对象,js侧dart侧都是,原理如下图。

上面是生成js文件的格局:

//该对象用于全局治理各个js对象let GLOBAL = {};...//'#FairKey#'会在发送之前全局对立替换GLOBAL['#FairKey#']={        ...    //转换之后的js内容,蕴含对应的变量和办法        ...}

二、通信协议的实现

js与布局文件的通信,实质上就是js与dart之间的通信,因为两者都是以native平台做依靠,所以须要native作为音讯的转发器,负责音讯的散发。对于dart与native之间的通信,咱们应用的是官网提供的message-channel与dart:FFI。message-channel次要有、BasicMessageChannel、MethodChannel、EventChannel,该通道次要用于异步通信,dart:FFI是官网提供的间接调用native c/c++代码的工具,次要用于同步通信。对于native与js之间的通信,咱们则能够用注入办法的模式建立联系,native侧注入本地办法,那么js则能够调用该办法发送音讯并获取后果值,而如果是js提供本地办法, 那native侧能够执行js中的办法获取js发送的后果。

2.1 格局定义

为了不便数据的对立解决,须要规定数据格式,数据的解决逻辑次要集中在js侧和dart侧,native侧只负责数据的转发,以及js的加载和开释。

{    pageName:"对应的调用页名称,也就是js侧的#FairKey#",    funcType:"调用类型,method,variable等",    args:{        //用户携带的参数,交由js侧解决    }}

2.2 通道创立

对于通道的创立,目前次要是用了三种,对于js的加载以及开释用的是MethodChannel,对于js-native之间的通信采纳的是BasicMessageChannel和dart:FFI。之所以采纳两种通道的起因,也是为了拆散加载开释和音讯发送的流程。

2.3 各侧接口定义

dart侧与js侧的分割,次要是波及到同步和异步通信,所以在设计接口办法的时候也是做了辨别。

为了抹平平台的差异性,对于js侧的通信通过的是办法的注入,native侧只负责音讯的转发,不做太多的逻辑,所以js侧只注入了一个办法,逻辑的解决交给js侧解决,办法会依据音讯的字段type来执行具体的操作。

三、JS文件的加载与开释

咱们通过对dsl布局的解析生成widget树,用于UI的展现,对于外面的逻辑,咱们放到js中去解决。所以咱们须要波及到js的加载,用于数据绑定,当页面销毁之后咱们对应的也须要开释掉js,升高对内存的耗费,同时须要防止出现反复加载问题。

3.1 js文件的加载流程

  1. js文件的加载次要分为以下几步:
  2. 读取本地或者网络的js数据
  3. 对js数据包装成固定格局的json字符串
  4. 通过method-channel调用native端的加载办法
  5. native端js引擎加载胜利之后返回音讯告诉dart端,js加载实现。

3.2 js文件的开释流程

当页面销毁之后,须要销毁js文件,耗费的步骤如下:

  1. 当widget的onDispose回调被执行的时候,调用dart侧的开释办法
  2. dart通过method-channel调用native端的开释办法
  3. native调用js侧办法,js会依据pageName获取制订的js对象,并将相干对象从汇合中移除,后续回收就交给js引擎解决了
  4. js移除胜利之后,native侧移除相干js-Object,实现开释
  5. 开释胜利之后告诉dart侧,开释过程实现。

四、数据的绑定

当js加载实现之后,接下来的工作就是须要做数据的绑定了。对于数据的绑定,绑定的是变量和办法,本文只写办法的调用,对于调用形式会在接下来的文章进行分享。在js加载实现之后,dart端会调用getBindVariableAndFunc办法获取js侧的数据,数据包包含js侧办法名称、变量名称、变量名对应的值。每一个FairWidget都会有一个固定的key,在js侧也会有一个对应的key,dart给js侧发送音讯的时候,会通过key获取js侧的对象,而后进行相干操作。dart中的布局被解析成给各个节点,对于外面的js逻辑则会解析成js,js代码中会对应具体的变量以及办法,所以咱们要做的是建设dart与js之间的通信,用于获取变量值,执行相干办法等。所有的数据都是在js侧解决,只有js调用setState的办法的时候才会将数据发送到dart侧,刷新页面数据。

获取js侧的数据格式如下:

{    "func":["办法A名","办法B名"],    "variable":{        "变量a名":"变量a值",        "变量b名":"变量b值",        "变量c名":"变量c值",           ...     }}

咱们获取到这些数据之后就会将数据绑定到RuntimeFairDelegate中去,当理论调用的时候从外面依据名称取出来对应数据绑定就能够了。

五、音讯散发

音讯的散发次要是指dart-js两者音讯可能正确的发送和接管,而保障音讯可能正确的接管发送,是通过FairKey来确定的。音讯的发送分两局部,一部分是js发送音讯给dart,当用js侧调用setState和调用invokeFlutterCommonChannel的时候会发送音讯给dart侧,其中invokeFlutterCommonChannel是native侧注入的办法,setState是js侧注入的办法,是对invokeFlutterCommonChannel的包装;另一部分是dart发送音讯给js侧,例如获取js侧绑定数据、调用js侧办法的时候,通过Runtime中的办法即可通信。对于dart侧办法的调用,会有同步调用和异步调用。异步调用的实现形式是通过message-channel,同步则通过dart:FFI的模式。 

5.1 通信过程

js通信dart(js发送音讯到dart次要是异步通信)通过调用js侧invokeFultterChanne办法,将音讯发送到native侧,native再对音讯转发至dart侧。

5.2 Dart侧音讯散发

当dart侧接管到js发送的音讯的时候,dart侧会对音讯分类,而后发送到正确的指标。对与dart侧音讯的接管次要是在message handler的中散发音讯,具体散发过程是依据音讯的funcName字段作辨别,来自js侧的音讯目前只有两种,一种是js侧拓展发送过去的音讯,第二种是setState发送过去的音讯。当funcName名称为invokePlugin的时候是用户拓展模块发来的音讯,对setState发送过去的音讯,会依据pageName散发到指定的FairWidget中去。

六、三方拓展

js转dart的时候,有些性能是js不反对的,例如dart端用的通信是Dio,权限调用,拍照,相册抉择,那么js转换的时候是有问题的,会因为没有找到对应的类而报错,js只做逻辑解决所以对于这些性能须要用户自定义封装,上面是封装根本流程:

6.1 封装Dart侧

封装dart侧,须要继承IFairPlugin接口,并实现外面的getRegisterMethods办法,如下所示。

//实现IFairPlugin的getRegisterMethods办法,暴露出对应js侧的办法class FairPhotoSelector extends IFairPlugin {  Future<dynamic> getPhoto(dynamic map) async {    //具体的逻辑实现,    return Future.value();  }  @override  Map<String, Function> getRegisterMethods() {    var functions = <String, Function>{};    //用户须要注册办法,这个办法与js侧对应    functions.putIfAbsent('getPhoto', () => getPhoto);    return functions;  }}

6.2 封装JS侧

js侧的封装比较简单,只须要调用invokeFlutterCommonChannel办法传递给Native,而后再传递给Dart侧,如下所示。

let FairPhotoSelectorCallback = {};let FairPhotoSelector = function () {        return {        getPhoto: function (req) {            //调用改办法,将包装的音讯发送到dart侧            invokeFlutterCommonChannel(JSON.stringify(reqMap), function (resp) {                //解决dart端返回的申请后果                    ...                //生产完之后及时移除回调                FairPhotoSelectorCallback[respCallId] = null;            });        },    };};

6.3 注册

在fair_basic_config.json中注册咱们自定义封装。

// fair_basic_config.json中注册{  "plugin": {      ...    "fair_photo": "assets/plugin/sample_fair_photo_selector.js"  }}//main函数中注册用户拓展void main() {  WidgetsFlutterBinding.ensureInitialized();  FairApp.runApplication(    _getApp(),    plugins: {        ...      'FairPhotoSelector': FairPhotoSelector(),    },  );}

6.4 根本应用

实现上述操作之后,接下来咱们就能够应用自定义的封装插件,如下。

//开始应用FairPhotoSelector().getPhoto({  //pageName为固定格局,不便转换成js之后替换相干值  'pageName': '#FairKey#',  'args': {    'type': 'album',    'success': (resp) {      picUrl = resp;      setState(() {});    },    'failure': () {      //用户获取图片失败    },  }});

基本原理就是js将js侧参数包装,通过invokeFlutterCommonChannel办法告诉到native侧,native侧再通过messagechannel将音讯发送到dart侧,dart侧解决完之后返回相干数据到js侧,js侧接管dart返回的数据,而后执行接下来的逻辑。音讯的发送也是通过自定义的音讯通道发送,dart会依据音讯中的func的值判断,如果是值为invokePlugin,那么则会通过FairPluginDispatcher分发给指定拓展类,其工作流程图如下。