找回密码
 会员注册
查看: 27|回复: 0

Pigeon-Flutter多端接口一致性以及规范化管理实践

[复制链接]

2万

主题

0

回帖

6万

积分

超级版主

积分
64454
发表于 2024-9-20 17:58:55 | 显示全部楼层 |阅读模式
导语: 跨端开发中,经常会遇到插件,接口管理上的问题。了解完本文,你将会了解Flutter是如何通过Pigeon去解决plugin中多端开发难以管理的问题。demo源码地址https://github.com/linpenghui958/flutterPigeonDemowarning:目前Pigeon还是prerelease版本,所以可能会有breakingchange。下文以0.1.7版本为例。为何需要Pigeon在hybird开发中,前端需要native能力,需要native双端开发提供接口。这种情况下就如何规范命名,参数等就成了一个问题,如果单独维护一份协议文件,三端依照协议文件进行开发,很容易出现协议更改后,没有及时同步,又或者在实际开发过程没有按照规范,可能导致各种意外情况。在Flutter插件包的开发中,因为涉及到native双端代码实现能力,dart侧暴露统一的接口给使用者,也会出现同样的问题,这里Flutter官方推荐使用Pigeon进行插件管理。Pigeon的作用Flutter官方提供的Pigeon插件,通过dart入口,生成双端通用的模板代码,Native部分只需通过重写模板内的接口,无需关心methodChannel部分的具体实现,入参,出参也均通过生成的模板代码进行约束。假设接口新增,或者参数修改,只需要在dart侧更新协议文件,生成双端模板,即可达到同步更新。以Flutter官方plugin中的video_player为例,接入pigeon后最终效果如下可以看到接入pigeon后整体代码简洁了不少,而且规范了类型定义。接下来我们看一下如何从零接入Pigeon。接入Pigeon先看一下pub.dev上Pigeon的介绍,Pigeon只会生成Flutter与native平台通信所需的模板代码,没有其他运行时的要求,所以也不用担心Pigeon版本不同而导致的冲突。(这里的确不同版本使用起来差异较大,笔者这里接入的时候0.1.7与0.1.10,pigeon默认导出和使用都不相同)创建packageps:如果接入已有plugin库,可以跳过此部分,直接看接入部分。执行生成插件包命令:flutter create --org com.exmple --template plugin flutterPigeonDemo要创建插件包,使用--template=plugin参数执行fluttercreatelib/flutter_pigeon_demo.dart插件包的dartapiandroid/src/main/kotlin/com/example/flutter_pigeon_demo/FlutterPigeonPlugin.kt插件包Android部分的实现ios/Classes/FlutterPigeonDemoPlugin.m插件包ios部分的实现。example/使用该插件的flutterdemo。这里常规通过methodChannel实现plugin的部分省略,主要讲解一下如何接入pigeon插件。添加依赖首先在pubspec.yaml中添加依赖dev_dependencies:  flutter_test:    sdk: flutter  pigeon:    version: 0.1.7然后按照官方的要求添加一个pigeons目录,这里我们放dart侧的入口文件,内容为接口、参数、返回值的定义,后面通过pigeon的命令,生产native端代码。这里以pigeons/pigeonDemoMessage.dart为例import 'package:pigeon/pigeon.dart';class DemoReply {  String result;}class DemoRequest {  String methodName;}// 需要实现的api@HostApi()abstract class igeonDemoApi {  DemoReply getMessage(DemoRequest params);}// 输出配置void configurePigeon(PigeonOptions opts) {  opts.dartOut = './lib/PigeonDemoMessage.dart';  opts.objcHeaderOut = 'ios/Classes/PigeonDemoMessage.h';  opts.objcSourceOut = 'ios/Classes/PigeonDemoMessage.m';  opts.objcOptions.prefix = 'FLT';  opts.javaOut =  'android/src/main/kotlin/com/example/flutter_pigeon_demo/PigeonDemoMessage.java';  opts.javaOptions.package = 'package com.example.flutter_pigeon_demo';}pigeonDemoMessage.dart文件中定义了请求参数类型、返回值类型、通信的接口以及pigeon输出的配置。这里@HostApi()标注了通信对象和接口的定义,后续需要在native侧注册该对象,在Dart侧通过该对象的实例来调用接口。configurePigeon为执行pigeon生产双端模板代码的输出配置。dartOut为dart侧输出位置objcHeaderOut、objcSourceOut为iOS侧输出位置prefix为插件默认的前缀javaOut、javaOptions.package为Android侧输出位置和包名之后我们只需要执行如下命令,就可以生成对应的代码到指定目录中。flutter pub run pigeon --input pigeons/pigeonDemoMessage.dart--input为我们的输入文件生成模板代码后的项目目录如下项目目录我们在Plugin库中只需要管理标红的dart文件,其余标绿的则为通过Pigeon自动生成的模板代码。我们接下来看一下双端如何使用Pigeon生成的模板文件。Android端接入这里Pigeon生产的PigeonDemoMessage.java文件中,可以看到入参和出参的定义DemoRequest、DemoReply,而PigeonDemoApi接口,后面需要在plugin中继承PigeonDemoApi并实现对应的方法,其中setup函数用来注册对应方法所需的methodChannel。ps:这里生成的PigeonDemoApi部分,setup使用了接口中静态方法的默认实现,这里需要apilevel24才能支持,这里需要注意一下。考虑到兼容性问题,可以将setup的定义转移到plugin中。首先需要在plugin文件中引入生成的PigeonDemoMessage中的接口和类。FlutterPigeonDemoPlugin先要继承PigeonDemoApi。然后在onAttachedToEngine中进行PigeonDemoApi的setup注册。并在plugin中重写PigeonDemoApi中定义的getMessage方法伪代码部分// ... 省略其他引入import com.example.flutter_pigeon_demo.PigeonDemoMessage.*// 继承PigeonDemoApipublic class FlutterPigeonDemoPlugin: FlutterPlugin, MethodCallHandler, igeonDemoApi { //... override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {     channel = MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "flutter_pigeon_demo")     channel.setMethodCallHandler(this);     // pigeon生成的api进行初始化     igeonDemoApi.setup(flutterPluginBinding.binaryMessenger, this);   }      // 重写PigeonDemoApi中的getMessage方法   override fun getMessage(arg: DemoRequest): DemoReply {      var reply = DemoReply();      reply.result = "pigeon demo result";      return reply; }}iOS接入ios相关目录下的PigeonDemoMessage.m也有FLTDemoReply、FLTDemoRequest、FLTPigeonDemoApiSetup的实现。首先需要在plugin中引入头文件PigeonDemoMessage.h,需要在registerWithRegistrar中注册setup函数,并实现getMessage方法。#import "FlutterPigeonDemoPlugin.h"#import "PigeonDemoMessage.h"@implementation FlutterPigeonDemoPlugin+ (void)registerWithRegistrarNSObject*)registrar {    FlutterPigeonDemoPlugin* instance = [[FlutterPigeonDemoPlugin alloc] init];    // 注册api    FLTPigeonDemoApiSetup(registrar.messenger, instance);}// 重写getMessage方法- (FLTDemoReply*)getMessageFLTDemoRequest*)input errorFlutterError**)error {    FLTDemoReply* reply = [[FLTDemoReply alloc] init];    reply.result = @"pigeon demo result";    return reply;}@endDart侧使用最终在dart侧如何调用呢首先看一下lib下Pigeon生成的dart文件PigeonDemoMessage.dartDemoReply、DemoRequest用来实例化入参和出参然后通过PigeonDemoApi的实例去调用方法。import 'dart:async';import 'package:flutter/services.dart';import 'PigeonDemoMessage.dart';class FlutterPigeonDemo {  static const MethodChannel _channel =      const MethodChannel('flutter_pigeon_demo');  static Future get platformVersion async {    final String version = await _channel.invokeMethod('getPlatformVersion');    return version;  }  static Future testPigeon() async {    // 初始化请求参数    DemoRequest requestParams = DemoRequest()..methodName = 'requestMessage';    // 通过PigeonDemoApi实例去调用方法    igeonDemoApi api = igeonDemoApi();    DemoReply reply = await api.getMessage(requestParams);    return reply;  }}至此,Pigeon的接入就已经完成了。接入Pigeon后的效果本文demo代码较为简单,接入Pigeon前后的差异并不明显,我们可以看下一Flutter官方plugin中的video_player接入前后的对比。左侧为接入Pigeon前,处理逻辑都在onMethodCall中,不同的方法通过传入的call.method来区分,代码复杂后很容易变成面条式代码,而且返回的参数也没有约定,有较多不确定因素。右侧接入Pigeon后,只需要重写对应的方法,逻辑分离,直接通过函数名区分,只需要关心具体的业务逻辑即可。而在dart的调用侧,接入前都是通过invokeMethod调用,传入的参数map内也是dynamic类型的值。接入后直接调用api的实例对象上的方法,并且通过Pigeon生成的模板代码,直接实例化参数对象。总结:通过Pigeon来管理Flutter的plugin库,只需要在dart侧维护一份协议即可,即使在多端协同开发的情况下,也能达到约束和规范的作用。在实现原生插件时我们可以省去很多重复代码,并且不需要关心具体methodchannel的name,也避免了常规情况下,可能出现的面条式代码,只需通过重写pigeon暴露的方法就可以完成双端的通信。而dart侧也只需要通过模板暴露的实例对象来调用接口方法。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-27 02:08 , Processed in 0.729430 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表