美文网首页Flutter
FIDL:Flutter与原生通讯的新姿势,不局限于基础数据类型

FIDL:Flutter与原生通讯的新姿势,不局限于基础数据类型

作者: 奶盖ww | 来源:发表于2020-03-17 20:14 被阅读0次

    大家好!今天给大家安利一个自认为比较重磅的Flutter开源项目。

    Flutter的产品定义是一个高性能的跨平台的移动UI框架,能够用一套代码同时构建出Android/iOS/Web/MacOS应用。作为一套UI框架,它不具备一些系统的接口,自然还是避免不了跟原生打交道。于是乎,它提出了名为platform channel的东西,用于flutter和原生灵活的交换数据。以下为了描述方便,用Android代指原生。

    燃鹅,燃鹅,燃鹅,它只支持一些基础的数据类型和数据结构的传输,例如bool/int/long/byte/char/String/byte[]/List/Map等。

    因此,当你想传输复杂点的数据,你只能包装成Map,类似这样:

    await _channel.invokeMethod('initUser',
        {'name': 'Oscar', 'age': 16, 'gender': 'MALE', 'country': 'China'});
    

    然后再在Android层hard code,解析出不同的key对应的不同数据。如果你是一个纯fluter项目,且以后也没有和原生打交道的打算,或者只是需要进行简单的交互,那这种做法也无可厚非。而当你的项目已经有很大的一部分原生代码或者你需要使用第三方不支持flutter的lib库的时候,就意味着你需要编写大量向上面那样的模板代码。可见效率低下,且可维护性差。这时,你会想,能传输对象就好了!

    而当你想传输对象时:

    抱歉,没门,只能给你一个尴尬又不是礼貌的危笑。当然,也不是不可以,我们可以在原生上层把对象序列化成json对象,然后在flutter层再把json转成flutter的对象,同样效率很差。

    FIDL是什么

    学过Android的应该都知道AIDL(Android Interface Defination Language),即Android接口定义语言。Android中有一种高级的跨进程通信方式——Binder,但是想要使用Binder需要了解一些Binder的机制和API,需要编写大量的模板代码。Android为了解决这个问题,尝试把使用Binder的方法做的小白一点。于是定义了AIDL,告诉开发者,你的接口文件必须按照我规定的来写,你要跨进程传输的对象必须实现Parcelable接口。然后,Android给你生成了一个Service.Stub类,偷偷的在背后把对象的序列化、反序列化的工作都给做了。开发者使用这个Stub类就能轻松上手Binder这种高级的跨进程通讯方法。(😋😋😋我编的,差不多啦)

    FIDL(Flutter Interface Defination Language)即Flutter接口定义语言,它的使命和AIDL很类似,悄悄把对象的序列化、反序列化、自动生成代码这种“脏活累活”给做了。开发者在原生代码中看到的类,能通过@FIDL注解标记,自动在Dart侧生成和原生代码中一样的类。FIDL是一面镜子,把各种原生平台的类影射到Dart中,把Dart中的类影射到各个原生平台。

    少啰嗦,先看东西

    1、首先是Java类:

    public class User {
      String name;
      int age;
      String country;
      Gender gender;
    }
    enum Gender {
      MALE, FEMALE
    }
    

    2、定义FIDL接口

    @FIDL
    public interface IUserService {
        void initUser(User user);
    }
    

    3、执行几个命令

    4、Android侧在合适的地方打开IUserServiceStub通道(IUserServiceStub是IUserService的实现类,自动生成的)

    FidlChannel.openChannel(getFlutterEngine().getDartExecutor(), new IUserServiceStub() {
      @Override
      void initUser(User user){
        System.out.println(user.name + " is " + user.age + "years old!");
      }
    }
    

    5、Flutter侧使用IUserService 通道

    // 绑定通道(IUserService类是自动生产的哦)
    await Fidl.bindChannel(IUserService.CHANNEL_NAME, _channelConnection);
    // 使用User类(`User类`以及它使用的`Gender枚举`是自动生成的哦)
    User user = User();
    user.name = 'Oscar';
    user.age = 18;
    user.gender = Gender.MALE;
    user.country = 'China';
    // 调用通道方法
    await IUserService.initUser(user);
    

    编译,运行,你将能在Logcat中看到Oscar is 18 years old!

    FIDL使用详解

    这一部分是对少啰嗦,先看东西部分的补充解释,观众姥爷们可以自行跳过。

    上面的例子中的Map,一般来说,在Java中会对应一个类:

    public class User {
      String name;
      int age;
      String country;
      Gender gender;
    }
    enum Gender {
      MALE, FEMALE
    }
    

    如果想让flutter传输这个对象而不用在flutter层手动去编写User这个类,以及编写fromJson/toJson方法,你可以这样做:

    1、定义一个接口,添加注解@FIDL。这个注解将告知annotationProcessor生成一些接口和类的描述文件。

    @FIDL
    public interface IUserService {
        void initUser(User user);
    }
    

    2、Android Studio点击sync,或者执行:

    ./gradlew assembleDebug
    

    然后就会产生一堆json文件,如下:

    这些json文件就是FIDL和类的描述文件。没错,也会同时生成User引用的Gender类的描述文件

    同时,还会生成IUserService的实现IUserServiceStub。即:

    • com.infiniteloop.fidl_example.IUserService.fidl.json
    • com.infiniteloop.fidl_example.User.json
    • com.infiniteloop.fidl_example.Gender.json
    • com.infiniteloop.fidl_example.IUserServiceStub.java

    3、进入到你的flutter项目,在lib目录下创建fidl目录,把上面的json文件拷贝到这个目录,然后执行:

    flutter packages pub run fidl_model
    

    然后就能在fidl目录下自动生成相关的dart类:

    即:

    • User.dart
    • Gender.dart
    • IUserService.dart

    4、使用

    a. Android侧在合适的地方打开IUserServiceStub通道

    FidlChannel.openChannel(getFlutterEngine().getDartExecutor(), new IUserServiceStub() {
      @Override
      void initUser(User user){}
    }
    

    b. Flutter侧绑定IUserService通道

    await Fidl.bindChannel(IUserService.CHANNEL_NAME, _channelConnection);
    

    c、Flutter调用通道方法

    await IUserService.initUser(User());
    

    d、Flutter可以在合适的时候接触绑定

    await Fidl.unbindChannel(IUserService.CHANNEL_NAME, _channelConnection);
    

    e、Android侧可以在合适的时候关闭通道

    FidlChannel.closeChannel(userServiceStub);
    

    当然,FIDL的功能不止于此

    1、多个参数的FIDL接口

    void init(String name, Integer age, Gender gender, Conversation conversation);
    

    2、带返回值的FIDL接口

    UserInfo getUserInfo();
    

    3、支持泛型类的生成

    public class User<T> {
      T country;
    }
    public class AUser<String>{}
    

    FIDL接口:

    void initUser(AUser user);
    

    将能在dart侧生成AUser和User类,且能保持继承关系。

    4、传递枚举

    void initEnum0(EmptyEnum e);
    String initEnum1(MessageStatus status);
    

    5、传递集合、Map

    void initList0(List<String> ids);
    void initList1(Collection<String> ids);
    void initList7(Stack<String> ids);
    void initList10(BlockingQueue ids);
    

    6、传递复杂对象。继承、抽象、泛型、枚举和混合类,来一个打一个。

    当然,FIDL能做的不止于此

    现在,FIDL项目只实现了从Dart侧调用Android侧的方法。还有以下工作要做:

    • Android侧调用Dart侧的方法
    • 其它平台和Flutter方法的互相调用
    • EventChannel,EventChannel本质上是可以通过MethodChannel实现的,问题不大

    搞定了对象传输,这些问题,都是小case啦。

    对于对象的序列化和反序列化

    为了能满足大佬们的定制化需求,我分别在Java侧和Flutter侧定义了序列化/反序列化的接口类。

    Java:

    public interface ObjectCodec {
        List<byte[]> encode(Object... objects);
        <T> T decode(byte[] input, TypeLiteral<T> type);
    }
    

    Dart:

    abstract class ObjectCodec {
      dynamic decode(Uint8List input);
      List<Uint8List> encode(List objects);
    }
    

    目前使用的是JsonObjectCodec,经过JSON的编解码,性能会稍差。后面还希望和小伙伴们一起努力,实现更高效的编解码。

    项目进度

    上述提到的功能,只要是从Flutter侧调用Java侧的方法相关的,大部分都已经实现了。

    我做了一个Demo,模拟了一个在Android侧依赖了IM(即时通讯)SDK,需要在Flutter侧聊天、获取消息、发消息的场景。以下是Demo的截图:

    1、首页,点击按钮调用Android侧方法,开启聊天服务

    2、聊天页面

    3、发一条消息给Lucy并获取和Lucy的聊天记录

    4、调用Android侧方法发送N条消息给Wilson并获取聊天记录

    作者:小玩童
    链接:https://juejin.im/post/5e6f23eef265da574f355950
    来源:掘金

    相关文章

      网友评论

        本文标题:FIDL:Flutter与原生通讯的新姿势,不局限于基础数据类型

        本文链接:https://www.haomeiwen.com/subject/rhexyhtx.html