共计 4790 个字符,预计需要花费 12 分钟才能阅读完成。
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 分发给指定拓展类,其工作流程图如下。