你是否还记得?那些年我们一起追过的(FIDL:Flutter界的AIDL)

前言

大家好!今天给大家安利一个自认为比较重磅的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中的类影射到各个原生平台。

少啰嗦,先看东西

首先是Java类:

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

Android侧

1、定义FIDL接口

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

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

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

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

Flutter侧

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

2、绑定Android侧的IUserServiceStub通道

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

3、调用公开方法

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侧

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

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

接口方法的限制如下:

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

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

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

3、在合适的地方打开通道,向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);

4、如有需要,可以在合适的地方关闭通道

FidlChannel.closeChannel(userService);

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

Flutter侧

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

flutter?packages?pub?run?fidl_model

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

即:

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

2、绑定Android侧的IUserServiceStub通道

bool connected = await Fidl.bindChannel(IUserService.CHANNEL_NAME, _channelConnection);

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

3、调用通道的公开方法

if (_channelConnection.connected) {
  await IUserService.initUser(User());
}

</>

4、如果不再需要使用这个通道了,可以解除绑定

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

当然,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项目只实现了从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://blog.51cto.com/14775360/2485685

时间: 2024-10-17 15:41:29

你是否还记得?那些年我们一起追过的(FIDL:Flutter界的AIDL)的相关文章

还记得那些年我们一起追逐的盛荟花园

那些年我们疯抢的盛荟花园,只是差了那么一点而与它失之交臂,为此而懊恼不已.现在机会来了,盛荟花园再度卷土重来,最后剩余优质房源引爆热潮,盛荟花园,就是这么任性!! 据其易房网最可靠消息,盛荟剩余房源住宅仅19套,一口价11888元/m2,商铺仅剩一套,一口价13888元/m2.最大的优惠留给对的你,你这次还要眼睁睁地错过盛荟吗?机不再来,还是赶紧行动吧!作为大亚湾最具价值的豪华大盘,拥有最优的配套,让你尽情享受人生!现在,小编再给诸位看客回顾一下盛荟花园的三大亮点: 一是超大面积大型楼盘:占地总

那些年我们一起追过的缓存写法(二)

引言 感谢园子里的同学对上一篇的支持,很高兴楼主的一些经验及想法能够对大家有一些帮助. 上次主要讨论缓存读写这块各种代码实现.本篇是就上次的问题接着来,继续看那些年我们各种缓存用法. 目录 一:缓存预热 二:多级缓存 2.1 介绍 2.2 线程缓存 2.3 内存缓存 2.4 文件缓存 2.5 分布式缓存 2.6 DB缓存 三:多层缓存 四:总结 一:缓存预热 上次有同学问过.在第一次加载时,我们的缓存都为空,怎么进行预热. 单机Web情况下,一般我们使用RunTimeCache.相对于这种情况下

还记得早起偷菜!朋友圈晒步?蓝鲸游戏背后的极端强迫症

据媒体报道,为期50天.以"做任务"形式诱导参与者完成各类自残行为甚至自杀的死亡游戏"蓝鲸"近期已传入国内社交平台,到5月末,已有极少数少年深陷游戏中,不能自拔.更有一些无良之人,开始借青少年对"蓝鲸"游戏的好奇心行诈骗之实.比如,借"带人进蓝鲸游戏真群"之名骗取女性用户裸照,随即以"不给钱就公开裸照"等威胁手段向女性用户索要钱财. 文/张书乐 人民网.人民邮电报专栏作者,著有<微博运营完全自学手册&

还记得吗

还记得我们第一次的相见吗? 皓月映照寒暄的脸庞, 心却彼此相拥着: 还记得我们第一次的相拥吗? 霜露飘洒冰冷的石桥, 心却彼此激荡着: 还记得我们第一次的同眠吗? 细雨敲打破败的铁窗, 心却默然厮守着: 还记得我们第一次的庆生吗? 陈俗撕碎未落的允诺, 心却终生懊悔着. --仅以此文献给宝贝老婆,2014.09.13

c#静态构造函数 与 构造函数 你是否还记得?

构造函数这个概念,在我们刚开始学习编程语言的时候,就被老师一遍一遍的教着.亲,现在你还记得静态构造函数的适用场景吗?如果没有,那么我们一起来复习一下吧. 静态构造函数是在构造函数方法前面添加了static关键字之后形成的,并且没有修饰符(public,private),没有参数. 静态构造函数有哪些特点呢: 静态构造函数没有修饰符修饰(public,private),因为静态构造函数不是我们程序员调用的,是由.net 框架在合适的时机调用的. 静态构造函数没有参数,因为框架不可能知道我们需要在函

那些年我们一起追过的缓存写法(三)

目录 一:分析设计 二:O(1)LRU实现 三:过期删除策略 四: 总结 一:分析设计 假设有个项目有一定并发量,要用到多级缓存,如下: 在实际设计一个内存缓存前,我们需要考虑的问题: 1:内存与Redis的数据置换,尽可能在内存中提高数据命中率,减少下一级的压力. 2:内存容量的限制,需要控制缓存数量. 3:热点数据更新不同,需要可配置单个key过期时间. 4:良好的缓存过期删除策略. 5:缓存数据结构的复杂度尽可能的低. 关于置换及命中率:我们采用LRU算法,因为它实现简单,缓存key命中率

谈谈asp.net MVC中的AppendTrailingSlash以及LowercaseUrls ,你还记得吗?

asp.net MVC是一个具有极大扩展性的框架,可以在从Url请求开始直到最终的html的渲染之间进行扩展,所以要学好还是需要了解框架的运行原理,推荐Artech. 今天我们回忆的不是MVC中的filter,也不是Controller的激活或者是Action的执行,或者是Url路由RouteData的生成,我们来回忆的是RouteTable.Routes  ,即全局路由表的两个属性.AppendTrailingSlash以及LowercaseUrls. AppendTrailingSlash的

《C#编程风格》还记得多少

<C#编程风格>还记得多少 开始实习之后,才发现自己是多么地菜.还有好多东西还要去学习. 公司很好,还可以帮你买书.有一天随口问了一下上司D,代码规范上面有什么要求.然后D在Amazon上面找到了这本书<C#编程风格(The Elements of C# Style)>(中英对照),让我直接买下开看,按上面的要求编写就可以了.书可以找秘书F去报销. 上个星期四在Amazon下单,周一才到.这书确实来的有点慢,没关系,我看的快.从周一到周五,用每天上下班在挤地铁(广州地铁你懂的)的时

还记得那种 喜欢到不行的感觉么?

今天 , 听人说 . 那种喜欢到不行的 感觉 .突然感到好心酸 .喜欢到不行的那种感觉是什么 ,就是可以为了他 不给自己留余地 . 不会再让别人进入到自己的心里.有种冲动 想好好跟他在一起, 甚至一生一世 .当然, 一生一世这种事情 谁也说不准 ,但是 至少那一刻是认真的 . 没有一点欺骗 .如今的爱情都是有模式的,今儿认识了,互相觉得人还不错,例如性格够正常,都长得还行,比如个头儿符合我的标准,还有家也是一个地方的,以后不用嫁的很远.工作稳定,年龄相当.不是悸动,不是心动,更没心跳加速,只是觉