当前位置: 首页 > 科技观察

FIDL:Flutter世界里的AIDL,不局限于基本数据类型

时间:2023-03-21 00:00:33 科技观察

前言大家好!今天给大家分享一个我觉得比较重要的Flutter开源项目。Flutter的产品定义是一套高性能的跨平台移动端UI框架,可以用一套代码同时构建Android/iOS/Web/MacOS应用。作为一套UI框架,它没有一些系统接口,自然免不了要和原版打交道。于是乎,它提出了一个叫做platformchannel的东西,用于flutter和native灵活交换数据。下文中,为了描述方便,均使用Android来指代native。烧鹅,烧鹅,烧鹅,它只支持传输一些基本的数据类型和数据结构,比如bool/int/long/byte/char/String/byte[]/List/Map等。因此,当你想传输复杂的数据,只能把它包装成一个Map,像这样:await_channel.invokeMethod('initUser',{'name':'Oscar','age':16,'gender':'MALE','国家':'中国'});然后在Android层硬编码,解析出不同key对应的不同数据。如果你是纯flutter项目,以后没有打算和native打交道,或者只是需要简单的交互,那么这种做法无可厚非。而当你的项目已经有很大一部分原生代码或者需要使用不支持flutter的第三方lib库时,就意味着你需要像上面这样写大量的模板代码。可见效率低,可维护性差。这时候你会想,能传递对象该多好啊!而当你要转移对象时:对不起,没办法,我只能给你一个尴尬不礼貌的微笑。当然,也不是没有可能。我们可以在native上层把对象序列化成json对象,然后在flutter层把json转成flutter对象,这样也是很低效的。什么是FIDL学过Android的人应该都知道AIDL(AndroidInterfaceDefinitionLanguage),也就是Android的接口定义语言。Android有一个高级的跨进程通信方式——Binder,但是如果要使用Binder,需要了解一些Binder机制和API,需要编写大量的模板代码。为了解决这个问题,Android试图把使用Binder的方法白一点。所以定义AIDL是为了告诉开发者,你的接口文件必须按照我的规定来写,你要跨进程传递的对象必须实现Parcelable接口。然后,Android为您生成一个Service.Stub类,在幕后秘密地序列化和反序列化对象。开发者可以使用这个Stub类轻松上手Binder这种高级的跨进程通信方式。FIDL(FlutterInterfaceDefinitionLanguage)即Flutter接口定义语言。它的使命与AIDL非常相似。它悄悄地做着对象序列化、反序列化和自动代码生成的“脏活”。开发者在原生代码中看到的类可以使用@FIDL注解进行标记,从而在Dart端自动生成与原生代码中相同的类。FIDL是一个镜像,将各个原生平台的类映射到Dart,将Dart中的类映射到各个原生平台。话不多说,先看Java类:publicclassUser{Stringname;intage;Stringcountry;Gendergender;}enumGender{MALE,FEMALE}Android端1.定义FIDL接口@FIDLpublicinterfaceIUserService{voidinitUser(Useruser);}2.执行命令./gradlewassembleDebug,生成IUserServiceStub类和fidl.json文件3.开启通道,暴露方法FidlChannel.openChannel(getFlutterEngine().getDartExecutor(),newIUserServiceStub(){@OverridevoidinitUser(Useruser){System.out.println(user.name+"is"+user.age+"yearsold!");}}Flutter端1.将fidl.json文件复制到fidl目录下,执行命令flutterpackagespubrunfidl_model,生成Dart接口类2。在Android端绑定IUserServiceStub通道awaitFidl.bindChannel(IUserService.CHANNEL_NAME,_channelConnection);3.调用public方法awaitIUserService.initUser(User());编译运行,可以看到Oscar18岁!在Logcat中。这部分FIDL详细信息led解说为了不啰嗦,先看东西部的补充解说,观众爷爷奶奶可以自行跳过。一般来说,上面例子中的Map对应Java中的一个类:publicclassUser{Stringname;intage;Stringcountry;Gendergender;}enumGender{MALE,FEMALE}如果想让flutter传输这个对象,不用在flutter层手动写User类并编写fromJson/toJson方法,你可以这样做:Android端1.定义一个接口并添加注解@FIDL。这个注解会告诉annotationProcessor为一些接口和类生成描述文件。@FIDLpublicinterfaceIUserService{voidinitUser(Useruser);}接口方法限制如下:由于dart不支持方法重载,所以接口中不能出现同名的方法参数。只支持实体类,不支持回调。由于JSON解码的限制,Java不需要参数。Constructor2.在AndroidStudio中点击sync,或者执行:./gradlewassembleDebug,会生成一堆json文件,如下:这些json文件是FIDL和类描述文件。没错,User引用的Gender类的描述文件也会同时生成。同时也会生成IUserService的实现IUserServiceStub。即:com.infiniteloop.fidl_example.IUserService.fidl.jsoncom.infiniteloop.fidl_example.User.jsoncom.infiniteloop.fidl_example.Gender.jsoncom.infiniteloop.fidl_example.IUserServiceStub.java限制:只能生成强引用关系FIDL文件,如果FIDL接口强引用的类的子类没有被FIDL接口强引用,则不会生成对应的描述文件。3.在合适的地方开启通道,将方法暴露给FlutterFidlChannel。openChannel(getFlutterEngine().getDartExecutor(),userService);4.如果需要,可以在合适的地方关闭通道FidlChannel.closeChannel(userService);closed消息会通知到Flutter端。Flutter端1.进入你的flutter工程,在lib目录下创建一个fidl目录,将上面的json文件复制到该目录下,然后执行:flutterpackagespubrunfidl_model然后在fidl目录下自动生成相关的dart类:即:User.dartGender.dartIUserService.dart2,在Android端绑定IUserServiceStub通道boolconnected=awaitFidl.bindChannel(IUserService.CHANNEL_NAME,_channelConnection);_channelConnection用于跟踪IUserService通道的连接状态,当通道连接成功时,其onConnected方法将被召回;当通道连接断开时,会回调其onDisconnected方法。3.调用通道的public方法if(_channelConnection.connected){awaitIUserService.initUser(User());}4.如果不再需要使用这个通道,可以解绑awaitFidl.unbindChannel(IUserService.CHANNEL_NAME,_channelConnection));当然,FIDL的功能不仅限于此1.多参数(Stringname、Integerage、Gendergender、Conversationconversation)的FIDL接口voidinit;2、带返回值的FIDL接口UserInfogetUserInfo();3.支持生成泛型类publicclassUser{Tcountry;}publicclassAUser{}FIDL接口:voidinitUser(AUseruser);将可以在dart端生成AUser和User类,并且可以保持继承关系。4.调用枚举voidinitEnum0(EmptyEnume);StringinitEnum1(MessageStatusstatus);5.调用集合,MapvoidinitList0(Listids);voidinitList1(Collectionids);voidinitList7(Stackids);voidinitList10(BlockingQueueids);6.传递复杂的对象。继承、抽象、泛型、枚举和mixin类,一个一个来玩。现在,FIDL项目只实现了Dart端调用Android端的方法。还有以下工作要做:Android端调用Dart端的方法。其他平台和Flutter方法相互调用EventChannel。EventChannel本质上可以通过MethodChannel来实现。问题不大。对象传输已解决。这些问题都是小案例。.对于对象的序列化和反序列化,为了满足大佬们的自定义需求,我分别在Java端和Flutter端定义了序列化/反序列化接口类。Java:publicinterfaceObjectCodec{Listencode(Object...objects);Tdecode(byte[]input,TypeLiteraltype);}Dart:abstractclassObjectCodec{dynamicdecode(Uint8Listinput);Listencode(Listobjects);}目前使用的是JsonObjectCodec,JSON编解码后性能会稍差。未来希望和小伙伴们一起实现更高效的编解码。项目进展上面提到的大部分功能,只要涉及到从Flutter端调用Java端的方法,都已经实现了。我做了一个demo,模拟了一个场景,Android端依赖IM(InstantMessaging)SDK,需要在Flutter端聊天、获取消息、发送消息。下面是演示截图:1.在首页点击按钮调用Android端方法打开聊天服务2.聊天页面3.给Lucy发送消息,获取与Lucy的聊天记录4.调用Android端方法向Wilson发送N条消息,并获取聊天记录。上一次做开源项目是3年前的事了。它是一个Android原生刷新控件,TwinklingRefreshLayout,github3.7kstars。后来因为工作原因,整天和AndroidFramework和C/C++打交道,精力都放在了公司的业务上,没有时间和精力去维护。所以我今天要发布的Flutter开源项目,就是通过社区的力量,和大家一起维护这个项目。我在GayHub上建立了一个组织,github.com/flutterFIDL(https://github.com/flutterFIDL)。待会儿,我会把这个项目开源,一两天之内,代码会放到这里,github.com/flutterFIDL(https://github.com/flutterFIDL/FIDL)。