网易首页 > 网易号 > 正文 申请入驻

Flutter混编工程之高速公路Pigeon

0
分享至

点击上方蓝字关注我,知识会给你力量

前面我们讲到了Flutter与原生通信使用的是BasicMessageChannel,完全实现了接口解耦,通过协议来进行通信,但是这样的一个问题是,多端都需要维护一套协议规范,这样势必会导致协作开发时的通信成本,所以,Flutter官方给出了Pigeon这样一个解决方案。

Pigeon的存在就是为了解决多端通信的开发成本。其核心原理就是通过一套协议来生成多端的代码,这样多端只需要维护一套协议即可,其它代码都可以通过Pigeon来自动生成,这样就保证了多端的统一。

官方文档如下所示。

https://pub.flutter-io.cn/packages/pigeon/install

首先,需要dev_dependencies中引入Pigeon:

dev_dependencies:
pigeon: ^1.0.15

接下来,在Flutter的lib文件夹同级目录下,创建一个.dart文件,例如schema.dart,这里就是通信的协议文件。

例如我们需要多端统一的一个实体:Book,如下所示。

import 'package:pigeon/pigeon.dart';

class Book {
String? title;
String? author;
}

@HostApi()
abstract class NativeBookApi {
List getNativeBookSearch(String keyword);

void doMethodCall();
}

这就是我们的协议文件,其中@HostApi,代表从Flutter端调用原生侧的方法,如果是@FlutterApi,那么则代表从原生侧调用Flutter的方法。

生成

执行下面的指令,就可以让Pigeon根据协议来生成相应的代码,下面的这些配置,需要指定一些文件目录和包名等信息,我们可以将它保存到一个sh文件中,这样更新后,只需要执行下这个sh文件即可。

flutter pub run pigeon \
--input schema.dart \
--dart_out lib/pigeon.dart \
--objc_header_out ios/Runner/pigeon.h \
--objc_source_out ios/Runner/pigeon.m \
--java_out ./android/app/src/main/java/dev/flutter/pigeon/Pigeon.java \
--java_package "dev.flutter.pigeon"

这里面比较重要的就是导入schema.dart文件,作为协议,再指定Dart、iOS和Android代码的输出路径即可。

正常情况下,生成完后的代码就可以直接使用了。

❝ Pigeon生成的代码是Java和OC,主要是为了能够兼容更多的项目。你可以将它转化为Kotlin或者Swift。 ❞
使用

就以上面这个例子,我们来看下如何根据Pigeon生成的代码来进行跨端通信。

首先,在Android代码中,会生成一个同名协议的接口,NativeBookApi,对应上面HostApi注解标记的协议名。在FlutterActivity的继承类中,创建这个接口的实现类。

private class NativeBookApiImp(val context: Context) : Api.NativeBookApi {

override fun getNativeBookSearch(keyword: String?): MutableList {
val book = Api.Book().apply {
title = "android"
author = "xys$keyword"
}
return Collections.singletonList(book)
}

override fun doMethodCall() {
context.startActivity(Intent(context, FlutterMainActivity::class.java))
}
}

这里顺便提一下,engine使用FlutterEngineGroup的方式进行创建,如果是其它方式,按照不同的方法获取engine对象即可。

class SingleFlutterActivity : FlutterActivity() {

val engine: FlutterEngine by lazy {
val app = activity.applicationContext as QDApplication
val dartEntrypoint =
DartExecutor.DartEntrypoint(
FlutterInjector.instance().flutterLoader().findAppBundlePath(), "main"
)
app.engines.createAndRunEngine(activity, dartEntrypoint)
}

override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
Api.NativeBookApi.setup(flutterEngine.dartExecutor, NativeBookApiImp(this))
}

override fun provideFlutterEngine(context: Context): FlutterEngine? {
return engine
}

override fun onDestroy() {
super.onDestroy()
engine.destroy()
}
}

初始化Pigeon的核心方法就是NativeBookApi中的setup方法,传入engine和协议的实现即可。

接下来,我们来看下如何在Flutter中调用这个方法,在有Pigeon之前,我们都是通过Channel,创建String类型的协议名来通信的,现在有了Pigeon之后,这些容易出错的String就都被隐藏起来了,全部变成了正常的方法调用。

在Flutter中,Pigeon自动创建了NativeBookApi类,而不是Android中的接口,在类中已经生成了getNativeBookSearch和doMethodCall这些协议中定义的方法。

List list = await api.getNativeBookSearch("xxx");
setState(() => _counter = "${list[0]?.title} ${list[0]?.author}");

通过await就可以很方便的进行调用了。可见,通过Pigeon进行封装后,跨端通信完全被协议所封装了,同时也隐藏了各种String的处理,这样就进一步降低了人工出错的可能性。

优化

在实际的使用中,Flutter调用原生方法来获取数据,原生侧处理好数据后回传给Flutter,所以在Pigeon生成的Android代码中,协议函数的实现是一个带返回值的方法,如下所示。

override fun getNativeBookSearch(keyword: String?): MutableList {
val book = Api.Book().apply {
title = "android"
author = "xys$keyword"
return Collections.singletonList(book)

这个方法本身没有什么问题,假如是网络请求,可以使用OKHttp的success和fail回调来进行处理,但是,如果要使用协程呢?

由于协程破除了回调,所以无法在Pigeon生成的函数中使用,这时候,就需要修改协议,给方法增加一个@async注解,将它标记为一个异步函数。

我们修改协议,并重新生成代码。

@HostApi()
abstract class NativeBookApi {
@async
List getNativeBookSearch(String keyword);

void doMethodCall();
}

这时候你会发现,NativeBookApi的实现函数中,带返回值的函数已经变成了void,同时提供了一个result变量来处理返回值的传递。

override fun getNativeBookSearch(keyword: String?, result: Api.Result>?)

这样使用就非常简单了,将返回值通过result塞回去就好了。

有了这个方法,我们就可以将Pigeon和协程配合起来使用,开发体验瞬间上升。

private class NativeBookApiImp(val context: Context, val lifecycleScope: LifecycleCoroutineScope) : Api.NativeBookApi {
override fun getNativeBookSearch(keyword: String?, result: Api.Result>?) {
lifecycleScope.launch {
try {
val data = RetrofitClient.getCommonApi().getXXXXList().data
val book = Api.Book().apply {
title = data.tagList.toString()
author = "xys$keyword"
result?.success(Collections.singletonList(book))
} catch (e: Exception) {
e.printStackTrace()

override fun doMethodCall() {
context.startActivity(Intent(context, FlutterMainActivity::class.java))
}
}

协程+Pigeon YYDS。

❝ 这里只介绍了Flutter调用Android的场景,实际上Android调用Flutter也只是换了个方向而已,代码都是类似的,这里不赘述了,那iOS呢?——我写Flutter,关iOS什么事。 ❞
拆解

在了解了Pigeon如何使用之后,我们来看下,这只「鸽子」到底做了些什么。

从宏观上来看,不管是Dart端还是Android端,都是生成了三类东西。

  • 数据实体类,例如上面的Book类

  • StandardMessageCodec,这是BasicMessageChannel的传输编码类

  • 协议接口\类,例如上面的NativeBookApi

在Dart中,数据实体会自动帮你生成encode和decode的代码,这样你获取出来的数据就不再是Channel中的Object类型了,而是协议中定义的类型,极大的方便了开发者。

class Book {
String? title;
String? author;

Object encode() {
final Map pigeonMap = {};
pigeonMap['title'] = title;
pigeonMap['author'] = author;
return pigeonMap;
}

static Book decode(Object message) {
final Map pigeonMap = message as Map;
return Book()
..title = pigeonMap['title'] as String?
..author = pigeonMap['author'] as String?;
}
}

在Android中,也是做的类似的操作,可以理解为用Java翻译了一遍。

下面是Codec,StandardMessageCodec是BasicMessageChannel的标准编解码器,传输的数据需要实现它的writeValue和readValueOfType方法。

class _NativeBookApiCodec extends StandardMessageCodec {
const _NativeBookApiCodec();
@override
void writeValue(WriteBuffer buffer, Object? value) {
if (value is Book) {
buffer.putUint8(128);
writeValue(buffer, value.encode());
} else {
super.writeValue(buffer, value);

@override
Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) {
case 128:
return Book.decode(readValue(buffer)!);

default:
return super.readValueOfType(type, buffer);

}
}
}

同样的,Dart和Android代码几乎一致,也很好理解,毕竟是一套协议,规则是一样的。

下面就是Pigeon的核心了,我们来看具体的协议是如何实现的,首先来看下Dart中是如何实现的,由于我们是从Flutter中调用Android中的代码,所以按照Channel的原理来说,我们需要在Dart中申明一个Channel,并处理其返回的数据。

如果你熟悉Channel的使用,那么这段代码应该是比较清晰的。

下面再来看看Android中的实现。Android侧是事件的处理者,所以需要实现协议的具体内容,这就是我们前面实现的接口,另外,还需要添加setMessageHandler来处理具体的协议。

这里有点意思的地方是那个Reply类的封装。

public interface Result {
void success(T result);
void error(Throwable error);

前面我们说了,在Pigeon中可以通过@async来生成异步接口,这个异步接口的实现,实际上就是这里处理的。

看到这里,你应该几乎就了解了Pigeon到底是如何工作的了,说白了实际上就是通过build_runner来生成这些代码,把脏活累活都自己吞下去了,我们看见的,实际上就是具体协议类的实现和调用。

题外话

所以说,Pigeon并不是什么非常高深的内容,但却是Flutter混编的一个非常重要的思想,或者说是Flutter团队的一个指导思想,那就是通过「协议」「模板」来生成相关的代码,类似的还有JSON解析的例子,实际上也是如此。

再讲的多一点,Android模块之间的解耦、模块化操作,实际上是不是也能通过这种方式来处理呢?所以说,大道至简,殊途同归,软件工程做到最后,实际上思想都是类似的,万物斗转星移,唯有思想永恒。

向大家推荐下我的网站 https://xuyisheng.top/ 点击原文一键直达

专注 Android-Kotlin-Flutter 欢迎大家访问

本文原创公众号:群英传,授权转载请联系微信(Tomcat_xu),授权后,请在原创发表24小时后转载。

< END >

作者:徐宜生

更文不易,点个“三连”支持一下

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
美官员摊牌:别管台湾了,要么接受中国统一,要么大家一起玩完!

美官员摊牌:别管台湾了,要么接受中国统一,要么大家一起玩完!

无悔的灿烂人生
2026-07-03 11:59:30
够狠!王励勤终于动真格的了,直接砍掉前主席的后花园

够狠!王励勤终于动真格的了,直接砍掉前主席的后花园

以茶带书
2026-06-21 16:00:21
要不是世界杯,恐怕没人知道中国在52万人的佛得角投入了多少

要不是世界杯,恐怕没人知道中国在52万人的佛得角投入了多少

莫地方
2026-07-03 00:06:18
再三强调:有颈动脉斑块的人,宁可出门去散步,也不要去做这6事

再三强调:有颈动脉斑块的人,宁可出门去散步,也不要去做这6事

芹姐说生活
2026-07-02 23:34:19
7月3日足球比赛4场比分胜负进球分享,阿根廷 vs 佛得角(世界杯)

7月3日足球比赛4场比分胜负进球分享,阿根廷 vs 佛得角(世界杯)

宝哥精彩赛事
2026-07-03 14:36:22
双色球第2026075期开奖后,游戏规则如何运行?

双色球第2026075期开奖后,游戏规则如何运行?

齐鲁壹点
2026-07-03 15:12:44
HTC手机不死:但公司已转型的你快不认识

HTC手机不死:但公司已转型的你快不认识

快科技
2026-07-03 09:24:05
人根本不用养老!看懂这个真相,余生少走十年弯路

人根本不用养老!看懂这个真相,余生少走十年弯路

椰青美食分享
2026-07-02 11:40:36
进2球不给MVP,送2助不给MVP,偏偏0球0助之人MVP,只因他18岁?

进2球不给MVP,送2助不给MVP,偏偏0球0助之人MVP,只因他18岁?

萌兰聊个球
2026-07-03 07:08:29
皇帝的一天怎样过?乾隆3点起床7点宠幸妃子,结束枯燥的一天

皇帝的一天怎样过?乾隆3点起床7点宠幸妃子,结束枯燥的一天

史之铭
2026-05-08 00:57:40
欧盟正式废除原有关税豁免政策

欧盟正式废除原有关税豁免政策

新浪财经
2026-07-02 17:52:37
大陆发出统一最强音后,赖清德回应,黄智贤不装了,对大陆摊牌了

大陆发出统一最强音后,赖清德回应,黄智贤不装了,对大陆摊牌了

娱乐圈的笔娱君
2026-07-03 15:04:32
以总理:“我想停止接受美国的援助”

以总理:“我想停止接受美国的援助”

环球时报国际
2026-07-03 08:15:11
十年死敌变新东家!杰伦布朗直播炮轰凯尔特人:交易全程毫无尊重

十年死敌变新东家!杰伦布朗直播炮轰凯尔特人:交易全程毫无尊重

夜白侃球
2026-07-03 11:37:07
74岁老人被脱落的400斤大门砸进ICU,全身多处骨折,家属:商家说是我妈的错

74岁老人被脱落的400斤大门砸进ICU,全身多处骨折,家属:商家说是我妈的错

大风新闻
2026-07-03 13:48:03
张天爱镂空礼服引发巨大争议,不少网友质疑造型尺度是否擦边

张天爱镂空礼服引发巨大争议,不少网友质疑造型尺度是否擦边

无处不风景love
2026-07-03 13:11:49
至少4次攻击太空通讯中心!俄版“星链”将取得突破,全力阻止?

至少4次攻击太空通讯中心!俄版“星链”将取得突破,全力阻止?

鹰眼Defence
2026-07-02 17:05:49
头条世界杯|不用进球就能统治比赛,亚马尔帮西班牙重回热门

头条世界杯|不用进球就能统治比赛,亚马尔帮西班牙重回热门

澎湃新闻
2026-07-03 07:16:31
终于有经济学家批评体制内退休金太高、加剧代际矛盾,评论区炸锅

终于有经济学家批评体制内退休金太高、加剧代际矛盾,评论区炸锅

慧翔百科
2026-06-23 08:47:02
上海一61岁男子邀24岁女子到家吃饭,强抱触摸对方敏感部位被拘,女方起诉索赔超9000元,法院判了

上海一61岁男子邀24岁女子到家吃饭,强抱触摸对方敏感部位被拘,女方起诉索赔超9000元,法院判了

扬子晚报
2026-07-03 12:34:24
2026-07-03 16:36:49
Android群英传
Android群英传
Android群英传
455文章数 921关注度
往期回顾 全部

科技要闻

特斯拉交付超预期7.4万辆,股价却大跌7.5%

头条要闻

网友买二手CCD相机 发现内存哈工大教授上百张老照片

头条要闻

网友买二手CCD相机 发现内存哈工大教授上百张老照片

体育要闻

C罗穿已故队友若塔球衣谢场 眼中含泪

娱乐要闻

海来阿木孕期出轨指控掀起全网热议

财经要闻

AI“鬼故事”不断,市场开始重估?

汽车要闻

方程豹钛9内饰曝光 用上了长联屏设计/下半年上市

态度原创

旅游
时尚
本地
健康
公开课

旅游要闻

师宗凤凰谷藏天然扩音器,不靠喇叭传声数里,科学界仍存未解谜团

夏天的裙子流行“剪一刀”,谁穿谁美!

本地新闻

这场穿越酉阳的光影之旅,张张都是壁纸!

这4类消化病患者 吃粘食管住嘴

公开课

李玫瑾:为什么性格比能力更重要?

无障碍浏览 进入关怀版