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

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

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

image

燃鹅,燃鹅,燃鹅,它只支持一些基础的数据类型和数据结构的传输,例如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库的时候,就意味着你需要编写大量向上面那样的模板代码。可见效率低下,且可维护性差。这时,你会想,能传输对象就好了!

而当你想传输对象时:

image

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

少啰嗦,先看东西

image

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文件,如下:

image

这些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类:

image

即:

  • 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侧方法,开启聊天服务

image

2、聊天页面

image

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

image

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

image

最后

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

那么今天我想发布的这个Flutter开源项目,是想通过社区的力量,和大家一起把项目维护下去。我在GayHub上建立了一个组织,https://github.com/flutterFIDL。稍晚一点时间,我会把项目开源出来,一两天内,代码会放在这里,https://github.com/flutterFIDL/FIDL。大家记得投币、点赞、收藏,一键3连(大家如果觉得这个项目能很好解决跨平台通信问题,给个star可以嘛😶)。阿不,我需要一个团队跟我一起发展这个项目,希望你熟悉Flutter开发,了解Android和Java开发,热爱开源,熟悉Flutter+iOS / Flutter + Web其中的一种,并有相关项目经历,加我vx: w354850839。

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容