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文件的加载流程
- js文件的加载次要分为以下几步:
- 读取本地或者网络的js数据
- 对js数据包装成固定格局的json字符串
- 通过method-channel调用native端的加载办法
- native端js引擎加载胜利之后返回音讯告诉dart端,js加载实现。
3.2 js文件的开释流程
当页面销毁之后,须要销毁js文件,耗费的步骤如下:
- 当widget的onDispose回调被执行的时候,调用dart侧的开释办法
- dart通过method-channel调用native端的开释办法
- native调用js侧办法,js会依据pageName获取制订的js对象,并将相干对象从汇合中移除,后续回收就交给js引擎解决了
- js移除胜利之后,native侧移除相干js-Object,实现开释
- 开释胜利之后告诉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分发给指定拓展类,其工作流程图如下。