您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
Pigeon在Flutter多端接口统一中的应用
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
Pigeon在Flutter多端接口统一中的应用
自猿其说Tech
2021-12-23
IP归属:未知
29520浏览
计算机编程
Flutter
Flutter 是 Google 开源的 UI 工具包,帮助开发者通过一套代码库高效构建多平台精美应用,支持移动、Web、桌面和嵌入式平台。但是因为Flutter更多的关注与UI的构建和绘制,因此,在Flutter应用的开发过程中如果需要使用特定平台的一些原生功能(比如消息通知、相册访问等),开发者往往需要写大量的代码来实现Flutter和原生功能之间的通信。 Flutter是通过channel 的方式与原生进行通信的,但是在多端开发的实践过程中,我们发现手动的开发channel很容易产生一些问题: 1. 各平台接口定义不统一,可读性和可维护性差 1. 对于channel接口中的异常情况考虑不周全,代码的健壮性低 1. 对于channel中存在集合(List、Map)等复杂的入参或者返回值时,需要复杂的代码逻辑进行数据结构的编码码与解码 为了解决上面这些问题,Flutter在1.20版本推出了Pigeon。Pigeon是一个代码生成工具,开发者可以通过它自动生成channel代码,使得开发Flutter和原生平台之间的通信更简单、安全和高效。Pigeon已经在Flutter官方的一些plugin中都有使用(包括video_player、webview_flutter等)。 ### 1 Pigeon原理 在使用pigeon的时候,需要开发者先实现一个类,类中需要定义好channel中需要实现的方法,例如: ``` import 'package:pigeon/pigeon.dart'; @HostApi(dartHostTestHandler: 'TestJdfBaseInfoHostApi') abstract class JdfBaseInfoIosMethodChannel { String getBuild(); String getClientVersion(); String getBundleIdentifier(); } ``` pigeon使用的时候,首先他会使用Analyzer for Dart解析channel的接口定义文件。Analyzer会返回接口定义文件的抽象语法树(AST)。pigeon获取AST之后,通过Analyzer提供的接口,可以获取到当前解析文件所定义的所有的API(方法)、Class、Enum等各种的数据结构的List或者Map。 ``` Root classes:[ apis:[ Api name:JdfBaseInfoIosMethodChannel location:ApiLocation.host methods:[ Method name:getBuild returnType:TypeDeclaration baseName:String isNullable:false typeArguments:[] arguments:[] objcSelector: isAsynchronous:false, Method name:getClientVersion returnType:TypeDeclaration baseName:String isNullable:false typeArguments:[] arguments:[] objcSelector: isAsynchronous:false, Method name:getBundleIdentifier returnType:TypeDeclaration baseName:String isNullable:false typeArguments:[] arguments:[] objcSelector: isAsynchronous:false, ] ] enums:[] ] ``` 而后面的主要工作就是逐个解析每个API的返回值、方法名、入参类型和入参名称等各种数据,有了这些参数,就可以逐个生成每个API对应的channel方法的注册、返回和解析等代码逻辑。除了可以生成Flutter侧的代码还可以生成iOS或者Android侧的代码。 虽然现在Pigeon只支持Java和Objc,其实我们可以在它现有的代码逻辑上进行修改,就可以生成Kotlin、Swift代码。甚至可以定制自己的Pigeon,在企业微信中,因为他们广泛使用proto,微信团队就对Pigeon代码就行了修改,生成的channel代码可以直接支持proto。 ### 2 使用步骤 1)定义需要实现的接口的方法名称、入参、返回值等 2)使用pigeon生成Flutter、iOS和Android三端的代码 - 首先会生成相关channel的注册方法 - 在iOS侧,pigeon会将用户定义的所有接口方法包含在一个协议(protocol)内 - 在Android侧,pigeon会将用户定义的所有接口方法包含在一个接口(interface)内 3)在iOS和Android的plugin注册的方法中,调用pigeon生成的注册方法 4)在Android侧实现pigeon定义的接口,在iOS侧实现pigeon生成的相应协议 ![](//img1.jcloudcs.com/developer.jdcloud.com/285d4efc-cf04-44ce-8895-6edf089ec2fb20211223135614.png) ### 3 代码实战 下面以baseInfo plugin作为示例,来演示如何在代码中使用Pigeon。 baseInfo plugin的主要功能就是在用户同意隐私协议以后,才可以获取设备的各种参数和信息,包括但不限于:ip地址、系统版本、设备ID等等。因此它会有大量的和原生的交互channel需要实现,是我们使用Pigeon特别适合的场合。 这个plugin的初始工程中的代码结构如下: ``` . ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── build ├── example ├── jdf_base_info_plugin_ios.iml ├── pubspec.lock ├── pubspec.yaml ├── ios │ ├── Assets │ ├── Classes │ │ ├── JdfBaseInfoPluginIosPlugin.h │ │ └── JdfBaseInfoPluginIosPlugin.m │ └── jdf_base_info_plugin_ios.podspec ├── lib │ ├── jdf_base_info_plugin_ios.dart │ └── src │ └── jdf_base_info_ios.dart └── test └── jdf_base_info_plugin_ios_test.dart ``` 1)在根目录下新建一个文件夹pigeon,并创建接口文件jdf_base_info_message_ios.dart,这个文件中定义的是我们channel中所有的接口: ``` import 'package:pigeon/pigeon.dart'; @HostApi(dartHostTestHandler: 'TestJdfBaseInfoHostApi') abstract class JdfBaseInfoIosMethodChannel { String getBuild(); String getClientVersion(); String getBundleIdentifier(); } ``` 2)使用pigeon生成代码: ``` flutter pub run pigeon \ --input pigeon/jdf_base_info_message_ios.dart \ --dart_out lib/src/jdf_base_info_ios_method_channel.dart \ --dart_test_out test/jdf_base_info_ios_method_channel.dart \ --objc_header_out ios/Classes/jdf_base_info.h \ --objc_source_out ios/Classes/jdf_base_info.m ``` 生成后的iOS代码: 1)channel的实现 ``` class JdfBaseInfoIosMethodChannel { JdfBaseInfoIosMethodChannel({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec<Object?> codec = _JdfBaseInfoIosMethodChannelCodec(); { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:@"dev.flutter.pigeon.JdfBaseInfoIosMethodChannel.getBuild" binaryMessenger:binaryMessenger codec:JdfBaseInfoIosMethodChannelGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(getBuildWithError:)], @"JdfBaseInfoIosMethodChannel api (%@) doesn't respond to @selector(getBuildWithError:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { FlutterError *error; NSString *output = [api getBuildWithError:&error]; callback(wrapResult(output, error)); }]; } else { [channel setMessageHandler:nil]; } } ..... } ``` 2)生成的协议的声明和定义 ``` /// The codec used by JdfBaseInfoIosMethodChannel. NSObject<FlutterMessageCodec> *JdfBaseInfoIosMethodChannelGetCodec(void); @protocol JdfBaseInfoIosMethodChannel - (nullable NSString *)getBuildWithError:(FlutterError *_Nullable *_Nonnull)error; - (nullable NSString *)getClientVersionWithError:(FlutterError *_Nullable *_Nonnull)error; - (nullable NSString *)getBundleIdentifierWithError:(FlutterError *_Nullable *_Nonnull)error; ``` 3)iOS工程中注册channel和实现协议的每个接口 ``` @implementation JdfBaseInfoPluginIosPlugin + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar { JdfBaseInfoPluginIosPlugin* instance = [JdfBaseInfoPluginIosPlugin new]; [registrar publish:instance]; JdfBaseInfoIosMethodChannelSetup(registrar.messenger, instance); } - (nullable NSString *)getBuildWithError:(FlutterError *_Nullable *_Nonnull)error{ return [JDBAppInfo getBuild]; } ``` 4)最终生成的整个工程的目录结构 ``` . ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── build ├── example ├── jdf_base_info_plugin_ios.iml ├── pubspec.lock ├── pubspec.yaml ├── ios │ ├── Assets │ ├── Classes │ │ ├── JdfBaseInfoPluginIosPlugin.h │ │ ├── JdfBaseInfoPluginIosPlugin.m │ │ ├── jdf_base_info.h ----pigeon生成文件 │ │ └── jdf_base_info.m ----pigeon生成文件 │ └── jdf_base_info_plugin_ios.podspec ├── lib │ ├── jdf_base_info_plugin_ios.dart │ └── src │ ├── jdf_base_info_ios.dart │ ├── jdf_base_info_ios_impl.dart │ ├── jdf_base_info_ios_method_channel.dart ----pigeon生成文件 │ └── jdf_base_info_ios_platform_addition.dart ├── pigeon │ ├── generate_method_channel.sh │ └── jdf_base_info_message_ios.dart └── test ├── jdf_base_info_ios_method_channel.dart ----pigeon生成文件 └── jdf_base_info_plugin_ios_test ``` 从整个过程可以看到,pigeon的使用其实还是很简单的。但是在实际项目使用过程中,我们还遇到了一些问题。 ### 4 存在的问题 对于现在pigeon最新版本1.0.9来说,在使用过程中存在着一些限制: 1. 开发者定义的接口参数和返回值都必须是非可空的。 1. 类中定义的属性必须是可空的。 1. 虽然支持范型,但是范型中的类型必须是可空的。 1. Channel返回的错误必须单独定义在返回值中,而不能通过PlatformException返回。 虽然现在的版本存在这些问题,但是对于绝大多数应用来说,都可以通过各种方式来规避这些问题。前三个限制可以通过在原生层和Flutter层对于入参和返回值进行校验来解决。 对于第四个问题,通过和pigeon开发者沟通过后,他们的建议是对于逻辑错误,实际上是不应该直接通过抛出PlatformException来处理的。因为: 1. PlatformException通常是针对于平台错误而使用的。 1. PlatformException的抛出和捕获是需要耗费系统资源的,常见的逻辑错误也不应该抛出此类错误。 1. 很多开发者往往会直接PlatformException或者直接通过try-catch直接来捕获而不做任何处理。 更合理的处理逻辑错误的方式应该是通过在返回值中加入code字段,Flutter侧根据不同的code来处理不同的逻辑错误。 ``` class Result { int? code; String? value; } @HostApi() abstract class Api { Result query(); } ``` 不过对于一些需要大量和原生平台通信的Plugin来说,Pigeon可以节省相当大的工作量。除之以外,接口的多平台统一和完善而统一的异常处理,对于开发大型APP来说也是它带来的一个非常重要的好处。 ### 5 Pigeon的后续规划 pigeon的开发者也对后续可能的优化和发展提出了一些展望,包括: #### 5.1 指定channel执行的线程 开发者可以指定某一个channel的执行线程:包括platform, ui, io, raster等, 包括Flutter在2.6版本将要支持的后台线程执行channel。 #### 5.2 编译系统的集成 现在必须开发者手动的执行代码生成命令。用户可能会忘记修改完代码后重新生成channel的代码。如果集成进编译系统,就不会存在这个问题。 ### 6 展望 pigeon虽然只是一种channel代码的生成工具,但是他给我们提供了一种自动生成重复代码的思路。比如我们工程中很多的网络请求相关代码或者其他的很多重复代码,其实都可以借鉴pigeon的思路,实现重复代码的自动生成,这样既可以减少工作量,也可以实现代码风格的统一以及保证代码的可靠性。 ------------ ###### 自猿其说Tech-京东物流技术发展部 ###### 作者: 郝宏伟(京东快递APP开发团队)
原创文章,需联系作者,授权转载
上一篇:高效编写Flutter页面最佳实践
下一篇:Java9-17新版本特性介绍
相关文章
Taro小程序跨端开发入门实战
Flutter For Web实践
配运基础数据缓存瘦身实践
自猿其说Tech
文章数
426
阅读量
2149964
作者其他文章
01
深入JDK中的Optional
本文将从Optional所解决的问题开始,逐层解剖,由浅入深,文中会出现Optioanl方法之间的对比,实践,误用情况分析,优缺点等。与大家一起,对这项Java8中的新特性,进行理解和深入。
01
Taro小程序跨端开发入门实战
为了让小程序开发更简单,更高效,我们采用 Taro 作为首选框架,我们将使用 Taro 的实践经验整理了出来,主要内容围绕着什么是 Taro,为什么用 Taro,以及 Taro 如何使用(正确使用的姿势),还有 Taro 背后的一些设计思想来进行展开,让大家能够对 Taro 有个完整的认识。
01
Flutter For Web实践
Flutter For Web 已经发布一年多时间,它的发布意味着我们可以真正地使用一套代码、一套资源部署整个大前端系统(包括:iOS、Android、Web)。渠道研发组经过一段时间的探索,使用Flutter For Web技术开发了移动端可视化编程平台—Flutter乐高,在这里希望和大家分享下使用Flutter For Web实践过程和踩坑实践
01
配运基础数据缓存瘦身实践
在基础数据的常规能力当中,数据的存取是最基础也是最重要的能力,为了整体提高数据的读取能力,缓存技术在基础数据的场景中得到了广泛的使用,下面会重点展示一下配运组近期针对数据缓存做的瘦身实践。
自猿其说Tech
文章数
426
阅读量
2149964
作者其他文章
01
深入JDK中的Optional
01
Taro小程序跨端开发入门实战
01
Flutter For Web实践
01
配运基础数据缓存瘦身实践
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号