继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

开源|Fair逻辑动态化通信实现

Fair技术团队
关注TA
已关注
手记 10
粉丝 4
获赞 4

 卜杰 58技术 

● 项目名称:Fair 2.0

● Github地址:https://github.com/wuba/fair

● 项目简介:Fair是为Flutter设计的动态化框架,可以通过Fair Compiler工具对Dart源文件的转化,使项目获得动态更新Widget的能力。Fair 2.0是为了解决 Fair 1.0版本的“逻辑动态化”能力不足。

Fair 逻辑动态化,是对一期布局动态化的增强。为了实现逻辑动态化,我们当时考虑了多种方案,方案主要集中在这三个方面,一种是对google提供的JIT进行裁切,第二种是自定义解析引擎,第三种借助js的能力。

方案对比

http://img1.sycdn.imooc.com/62d4d73f00014dfd10800746.jpg

下面主要讲一下几方面:

  1. 架构的标准化

  2. 通信协议的实现

  3. js文件的加载与释放

  4. 数据的绑定

  5. 消息的分发

  6. 第三方扩展(用户根据需要扩展更精彩的功能)

在介绍之前,我们先了解一下dart转化之后的产物,一个dart文件会被转换为两部分,一部分是json结构的布局文件,而逻辑部分则会转换为js文件。

架构的标准化

当我们生成了布局文件和逻辑文件,接下来要做的是如何建立他们之间的联系,确切的说是如何建立两者之间的通信。为了方便资源的控制与分配,fair是对每一个逻辑js通过唯一的key确认,然后通过key-value的形式将逻辑文件临时储存在一个全局的集合中。因这个key在生成js的时候是不确定的,所以我们对key统一定义名称为#FairKey# ,在js发送到native侧之前,由dart侧全局替换该值为具体的key。这个key非常重要,在消息通信的时候,需要通过key获取具体的通信对象,js侧dart侧都是。

http://img1.sycdn.imooc.com/62d4d7b10001abfd07490569.jpg

下面是生成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发送的结果。

a)格式定义

为了方便数据的统一处理,需要规定数据格式,数据的处理逻辑主要集中在js侧和dart侧,native侧只负责数据的转发,以及js的加载和释放。

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

b)通道创建

对于通道的创建,目前主要是用了三种,对于js的加载以及释放用的是MethodChannel,对于js-native之间的通信采用的是BasicMessageChannel和dart:FFI。之所以采用两种通道的原因,也是为了分离加载释放和消息发送的流程。

c)各侧接口定义

dart侧与js侧的联系,主要是涉及到同步和异步通信,所以在设计接口方法的时候也是做了区分。


http://img2.sycdn.imooc.com/62d4d9700001ad8307490393.jpg

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

http://img1.sycdn.imooc.com/62d4d9a20001f98707490354.jpg

js文件的加载与释放

我们通过对dsl布局的解析生成widget树,用于UI的展示,对于里面的逻辑,我们放到js中去处理。所以我们需要涉及到js的加载,用于数据绑定,当页面销毁之后我们对应的也需要释放掉js,降低对内存的消耗,同时防止出现重复加载问题。

a)js文件的加载流程

  1. 读取本地或者网络的js数据

  2. 对js数据包装成固定格式的json字符串

  3. 通过method-channel调用native端的加载方法

  4. native端js引擎加载成功之后返回消息通知dart端,js加载完成。

http://img2.sycdn.imooc.com/62d4da3c0001ad3d07500594.jpg

数据的绑定

当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的形式。

a) 通信过程

http://img1.sycdn.imooc.com/62d4fb470001b1e807490348.jpg

dart通信js

js通信dart(js发送消息到dart主要是异步通信)通过调用js侧invokeFultterChanne方法,将消息发送到native侧,native再对消息转发至dart侧。

b) dart侧消息的分发

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

http://img1.sycdn.imooc.com/62d4fbb50001a9c907490801.jpg

三方拓展

js转dart的时候,有些功能是js不支持的,例如dart端用的通信是Dio,权限调用,拍照,相册选择,那么js转换的时候是有问题的,会因为没有找到对应的类而报错,js只做逻辑处理所以对于这些功能需要用户自定义封装,下面是封装基本流程:

a) 封装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;  
  }
}

b)封装JS侧

let FairPhotoSelectorCallback = {};
let FairPhotoSelector = function () {    
       return {
       
       getPhoto: function (req) {
           //调用改方法,将包装的消息发送到dart侧            invokeFlutterCommonChannel(JSON.stringify(reqMap), function (resp) {                //处理dart端返回的请求结果                    ...                //消费完之后及时移除回调                FairPhotoSelectorCallback[respCallId] = null;            });        },
   };
};

c) 注册

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

d) 开始使用

//开始使用
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分发给指定拓展类。

http://img3.sycdn.imooc.com/62d4fc070001991d07500697.jpg

消息发送流程

http://img1.sycdn.imooc.com/62d4fc2c000194b307500591.jpg

主要类之间的关系

在本文中主要介绍了Fair逻辑动态化通信实现,文中如有问题,也欢迎大家指出纠正。


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP