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

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

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

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

所以,当你想传输复杂点的数据,你只能包装成Map,相似这样:shell

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

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

而当你想传输对象时:bash

抱歉,没门,只能给你一个尴尬又不是礼貌的危笑。固然,也不是不能够,咱们能够在原生上层把对象序列化成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中的类影射到各个原平生台。ide

少啰嗦,先看东西

首先是Java类:函数

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

Android侧

一、定义FIDL接口

@FIDL
public interface IUserService {
	void initUser(User user);
}
复制代码

二、执行命令./gradlew assembleDebug,生成IUserServiceStub类和fidl.json文件

三、打开通道,向Flutter公开方法

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

Flutter侧

一、拷贝fidl.json文件到fidl目录,执行命令flutter packages pub run fidl_model,生成Dart接口类

二、绑定Android侧的IUserServiceStub通道

await Fidl.bindChannel(IUserService.CHANNEL_NAME, _channelConnection);
复制代码

三、调用公开方法

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方法,你能够这样作:

Android侧

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

@FIDL
public interface IUserService {
	void initUser(User user);
}
复制代码

接口方法的限制以下:

  • 因为dart不支持方法重载,因此接口中不能出现同名方法
  • 参数只支持实体类,不支持回调
  • 因为JSON解码的限制,Java须要有无参构造函数

二、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

限制:只能生成有强引用关系的FIDL文件,被FIDL接口强引用的类的子类若是没有被FIDL接口强引用,则不会生成相应的描述文件。

三、在合适的地方打开通道,向Flutter公开方法

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

四、若有须要,能够在合适的地方关闭通道

FidlChannel.closeChannel(userService);
复制代码

关闭的消息将通知到Flutter侧。

Flutter侧

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

flutter packages pub run fidl_model
复制代码

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

即:

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

二、绑定Android侧的IUserServiceStub通道

bool connected = await Fidl.bindChannel(IUserService.CHANNEL_NAME, _channelConnection);
复制代码

_channelConnection用于跟踪IUserService通道的链接状态,通道链接成功时,会回调它的onConnected方法;通道链接断开时,会回调它的onDisconnected方法。

三、调用通道的公开方法

if (_channelConnection.connected) {
  await IUserService.initUser(User());
}
复制代码

四、若是再也不须要使用这个通道了,能够解除绑定

await Fidl.unbindChannel(IUserService.CHANNEL_NAME, _channelConnection);
复制代码

固然,FIDL的功能不止于此

一、多个参数的FIDL接口

void init(String name, Integer age, Gender gender, Conversation conversation);
复制代码

二、带返回值的FIDL接口

UserInfo getUserInfo();
复制代码

三、支持泛型类的生成

public class User<T> {
  T country;
}
public class AUser<String>{}
复制代码

FIDL接口:

void initUser(AUser user);
复制代码

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

四、传递枚举

void initEnum0(EmptyEnum e);
String initEnum1(MessageStatus status);
复制代码

五、传递集合、Map

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

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

固然,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的截图:

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

二、聊天页面

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

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

最后

上次作开源项目已是3年前了,那是一个Android原生刷新控件,TwinklingRefreshLayout,github 3.7k stars。后来因为工做的缘由,成天跟Android Framework、C/C++打交道,精力也都是放到了公司的业务上,也没有时间和精力维护下去。

那么今天我想发布的这个Flutter开源项目,是想经过社区的力量,和你们一块儿把项目维护下去。我在GayHub上创建了一个组织,github.com/flutterFIDL。FIDL的代码在这里,github.com/flutterFIDL…。你们记得投币、点赞、收藏,一键3连(你们若是以为这个项目能很好解决跨平台通讯问题,给个star能够嘛😶)。阿不,我须要一个团队跟我一块儿发展这个项目,但愿你熟悉Flutter开发,了解Android和Java开发,热爱开源,熟悉Flutter+iOS / Flutter + Web其中的一种,并有相关项目经历,加我vx: w354850839。

这样一个库,香吗?告诉我,有多香。😉

欢迎留言评论,告诉我你的Flutter和原生通讯的使用场景,以及遇到的痛点和问题~

相关文章
相关标签/搜索