Pigeon 代码生成器实战指南:让 Flutter 与原生平台的通信类型安全、简单且高效
【免费下载链接】packagesA collection of useful packages maintained by the Flutter team项目地址: https://gitcode.com/GitHub_Trending/pac/packages
Pigeon 是 Flutter 官方维护的代码生成工具,它读取一份用 Dart 编写的接口定义文件,自动生成 Dart 与宿主平台(Android / iOS / macOS / Windows / Linux)之间通信所需的全部胶水代码,让跨语言调用变得类型安全、简单且高效。读完本文,你将掌握 Pigeon 的完整使用流程:如何编写通信接口、如何配置生成参数、如何接入各平台、如何处理异步与错误、如何选择 Platform Channels 与 Native Interop 两种通信模型,以及如何规避生成代码跨版本兼容的陷阱。
Pigeon 是什么:为什么需要它
在传统 Flutter 插件开发中,Dart 与原生代码之间通过 MethodChannel 传递消息,开发者需要手工维护通道名、方法名字符串以及两端的序列化/反序列化逻辑,字符串散落在多个平台、多种语言中,极易出错。Pigeon 的核心价值正是解决这些问题:
- 消除跨平台字符串管理:通道名、方法名、参数键名统一由一份 Dart 定义文件生成,不再需要手工在两端维护字符串。
- 类型安全:Dart 与原生侧都生成强类型的类与方法签名,编译期即可发现类型不匹配。
- 更高效率:相比手写 MethodChannel 常见模式,Pigeon 生成的代码更高效。
- 免写样板代码:Pigeon 自动生成平台通道(Platform Channel)或原生互操作(Native Interop)代码,开发者不再需要手写自定义通道与桥接逻辑。
在 Pigeon 官方定义中,"Pigeon is a code generator tool to make communication between Flutter and the host platform type-safe, easier, and faster",即它是让 Flutter 与宿主平台之间通信"类型安全、更简单、更快"的代码生成工具。
核心特性总览
Pigeon 的功能特性可以从平台支持、数据类型、同步/异步方法、错误处理、TaskQueue、多实例支持、通信模型选择与常量生成几个维度理解。
支持的平台与生成语言
目前 Pigeon 支持为以下平台生成代码:
| 平台 | 生成语言 |
|---|---|
| Android | Kotlin 与 Java |
| iOS 与 macOS | Swift 与 Objective-C |
| Windows | C++ |
| Linux | GObject(基于 C/C++ 的 GObject 代码) |
支持的数据类型
Pigeon 基于StandardMessageCodec,因此支持 平台通道所支持的任何数据类型。除此之外还支持:
- 自定义类(Custom classes)
- 嵌套数据类型(Nested datatypes)
- 枚举(Enums)
在类型层面有以下需要留意的行为:
- 基础继承:仅允许使用空的
sealed父类做基础继承,且只在 Swift、Kotlin 与 Dart 三个生成器中支持。 - Objective-C 可空枚举:生成代码中可空的枚举会被包装在一个类中以支持可空性。
- Swift 类的生成策略:默认情况下,Swift 生成的自定义类是 struct(结构体),而 struct 不支持某些特性——例如递归数据、Objective-C 互操作。若需要生成 Swift class,应在定义类时使用
@SwiftClass注解。
同步与异步方法
从 Flutter 的角度看,所有跨平台通道 API 的调用(如 Pigeon 方法)都是异步的;但 Pigeon 方法在原生侧可以写成同步方法,从而更简单地保证"恰好回复一次"(always reply exactly once)。如果需要异步方法,Pigeon 提供了两个注解:
@async:生成现代并发签名——Kotlin 生成suspend函数,Swift 生成async方法。这是异步方法的默认风格。@asyncCallback:生成基于完成回调的异步方法,例如接受一个(Result<T>) -> Unit或 completion closure 参数。
[!NOTE] 目前只有 Kotlin 与 Swift 两个生成器区分
@async与@asyncCallback;在 Java、Objective-C、C++ 与 GObject 生成器中,两种注解生成的是相同的基于回调的异步方法签名。
错误处理
Pigeon 将宿主侧(Host)API 抛出的异常统一翻译为 Flutter 的PlatformException,但不同语言的处理姿势不同:
Kotlin 与 Swift
- 同步方法与现代
@async方法:抛出的异常(Kotlin 为FlutterError,Swift 为PigeonError)会被自动捕获并翻译为PlatformException。 - 回调风格的
@asyncCallback方法:错误应通过提供的 result 回调返回(例如Result.failure(...))。 - 如需向
PlatformException传递自定义 details,Kotlin 使用FlutterError,Swift 使用PigeonError。
Java
- 同步方法:抛出的异常(
FlutterError)会被捕获并翻译。 - 异步方法(
@async与@asyncCallback):错误应通过提供的回调返回。
Objective-C 与 C++
宿主 API 错误可通过提供的FlutterError类发送(会被翻译为PlatformException):
- 同步方法:Objective-C 将
error参数设置为一个FlutterError引用;C++ 直接返回一个FlutterError。 - 异步方法(
@async与@asyncCallback):通过提供的回调返回FlutterError。
TaskQueue:选择线程模型
当目标 Flutter 版本支持 TaskQueue API 时,可以通过TaskQueue注解选择处理 HostApi 方法的线程模型。
注意:
TaskQueue仅支持平台通道模式。Native Interop(FFI/JNI)调用直接在执行调用的线程上运行,如果在 Native Interop 场景指定@TaskQueue,代码生成时会直接报错。
多实例支持
Host 与 Flutter API 都支持为 API 提供一个唯一的消息通道后缀字符串(message channel suffix),从而允许创建多个实例并并行独立运行。这在需要同时管理多个通道实例的场景(如多 WebView、多引擎)下非常有用。
通信模型二选一:Platform Channels 与 Native Interop
Pigeon 支持两种截然不同的 Dart 与原生代码通信模型:
- Platform Channels(平台通道):Flutter 标准通信模型。通过
StandardMessageCodec将数据序列化为二进制缓冲区,再经平台通道异步传输。 - Native Interop(直接 FFI 与 JNI)*实验性*:直接、内存绑定的函数调用模型——iOS/macOS 上使用 Dart FFI(针对 Swift/Objective-C),Android 上使用 JNI(针对 Kotlin/Java)。
下表是两种模型的核心差异对比:
| 特性 | Platform Channels | Native Interop |
|---|---|---|
| 通信机制 | 经平台通道的异步消息传递 | 直接内存绑定函数调用(Dart FFI / JNI) |
| 平台支持 | 全部支持平台(Android、iOS、macOS、Windows、Linux) | 仅 Android、iOS、macOS |
| 线程模型 | 主 UI 线程或自定义后台TaskQueue | 直接在调用者线程执行(不支持TaskQueue) |
| Dart Isolate 支持 | 需要BackgroundIsolateBinaryMessenger | Host API 直接支持 |
| 序列化开销 | 高(序列化与多次拷贝) | 低(几乎零拷贝) |
| 延迟 | 较高(需要消息循环调度) | 极低(直接执行) |
| 同步宿主调用 | 不支持 | 完全支持 |
| 搭建复杂度 | 简单 | 复杂(需要外部工具链) |
| 代码生成步骤 | 单步(运行 Pigeon 一次生成全部代码) | 多步(运行 Pigeon 会自动运行生成的配置脚本) |
如何选择?
- 优先考虑 Platform Channels,如果:
- 插件需要支持 Windows 或 Linux(Native Interop 不支持这些平台;不过你可以在同一份 Pigeon 文件中为它们生成平台通道代码,与 Native Interop 共存);
- 插件主要传递简单数据对象、通信频率低;
- 希望搭建简单,不引入外部依赖或额外命令行工具;
- 数据类包含大量嵌套字段或自定义集合,转换开销可能抵消性能收益(该问题有后续改进计划)。
- 优先考虑 Native Interop,如果:
- 插件需要高频消息、大型类型化数组(例如图像处理、传感器数据流)或对延迟敏感、序列化开销成为瓶颈的通信;
- 需要在宿主线程上对平台 API 做同步执行;
- 想从后台 Dart isolate 直接调用 Host API,而无需初始化 Flutter Engine 通道。
关于 Native Interop 的详细搭建、前置条件与使用说明,见 Native Interop Guide,迁移场景可参考 Native Interop 迁移指南。
常量生成
Pigeon 支持在生成文件中生成顶层常量。常量定义在 Pigeon 文件的顶层,例如来自示例文件 messages.dart:
const String aStringConstant = 'stringConstantValue'; const int anIntConstant = 42; const double aDoubleConstant = 3.14; const bool aBoolConstant = true;这些常量会被翻译为目标语言中的静态常量或 final 变量(例如 Java 中为public static final,Swift 中为let,Dart 中为const等)。目前仅支持String、int、double与bool四种常量类型。
使用流程:六步接入
Pigeon 的基本使用流程如下:
- 将 pigeon 添加为
dev_dependency。 - 在
lib目录之外创建一个.dart文件,用于定义通信接口。 - 对
.dart文件运行 pigeon,生成所需的 Dart 与宿主语言代码:先flutter pub get,再使用合适参数执行dart run pigeon。具体调用示例见 Invocation。 - 将生成的 Dart 代码放入
./lib参与编译。 - 实现宿主语言代码并加入构建(详见下文各平台步骤)。
- 调用生成的 Dart 方法。
配置 Pigeon 与命令行调用
在.dart输入文件的顶部通过@ConfigurePigeon注解配置生成目标。以下是来自示例应用 example/app/pigeons/messages.dart 的完整配置,实际使用中只需保留项目需要的语言:
@ConfigurePigeon( PigeonOptions( dartOut: 'lib/src/messages.g.dart', dartOptions: DartOptions(), cppOptions: CppOptions(namespace: 'pigeon_example'), cppHeaderOut: 'windows/runner/messages.g.h', cppSourceOut: 'windows/runner/messages.g.cpp', gobjectHeaderOut: 'linux/messages.g.h', gobjectSourceOut: 'linux/messages.g.cc', gobjectOptions: GObjectOptions(), kotlinOut: 'android/app/src/main/kotlin/dev/flutter/pigeon_example_app/Messages.g.kt', kotlinOptions: KotlinOptions(), javaOut: 'android/app/src/main/java/io/flutter/plugins/Messages.java', javaOptions: JavaOptions(), // 注意:swiftOut 也可以是列表,以便在需要时输出到独立的 iOS 与 macOS 位置。 swiftOut: 'ios/Runner/Messages.g.swift', swiftOptions: SwiftOptions(), objcHeaderOut: 'macos/Runner/messages.g.h', objcSourceOut: 'macos/Runner/messages.g.m', // 按照 Objective-C 命名规范,设置为插件或应用唯一的类名前缀。 objcOptions: ObjcOptions(prefix: 'PGN'), copyrightHeader: 'pigeons/copyright.txt', dartPackageName: 'pigeon_example_package', ), )各关键配置项的作用:
dartOut:生成的 Dart 代码输出路径(约定放到lib/下的src/中参与编译)。dartOptions/cppOptions/gobjectOptions/kotlinOptions/javaOptions/swiftOptions/objcOptions:各语言生成器选项,例如CppOptions(namespace: ...)指定 C++ 命名空间、ObjcOptions(prefix: 'PGN')指定 Objective-C 类名前缀。cppHeaderOut/cppSourceOut:C++ 头文件与源文件输出路径(Windows)。gobjectHeaderOut/gobjectSourceOut:GObject 头文件与源文件输出路径(Linux)。kotlinOut/javaOut:Kotlin 与 Java 输出路径(Android)。swiftOut:Swift 输出路径,可以是单个字符串,也可以是列表以分别输出到 iOS 与 macOS。objcHeaderOut/objcSourceOut:Objective-C 头文件与源文件输出路径。copyrightHeader:生成的代码头部版权声明文件路径。dartPackageName:生成的 Dart 代码所属包名。
配置完成后,只需要一条命令即可生成全部代码:
dart run pigeon --input path/to/input.dart注意:PigeonOptions、DartOptions、CppOptions、GObjectOptions、KotlinOptions、ObjcOptions、SwiftOptions等类型均由package:pigeon/pigeon.dart导出,见 lib/pigeon.dart,因此定义文件需要import 'package:pigeon/pigeon.dart';。
定义通信接口的规则
通信接口的定义遵循以下规则(完整示例见 HostApi Example):
- 只声明不实现:文件中不应包含方法或函数定义,只有声明。
- 自定义类:API 使用的自定义类用"字段类型为受支持数据类型"的类来定义(见上文"支持的数据类型")。
- API 形态:API 定义为
abstract class,并用元数据标注——@HostApi()表示该方法由宿主平台定义实现、Flutter 调用;@FlutterApi()表示该方法由 Dart 定义实现、宿主平台调用。 - 方法签名:API 类中的方法声明,其参数与返回值类型必须是在本文件中定义的类、受支持数据类型,或者是
void。 - 事件通道:仅 Swift、Kotlin 与 Dart 三个生成器支持事件通道(Event Channel)。
- 事件通道封装:事件通道方法应包裹在带
@EventChannelApi元数据的abstract class中。 - 事件流类型:事件通道定义中不应包含
Stream返回类型,只声明被流式传输的元素类型。 - 命名约定:Objective-C 与 Swift 有特殊命名约定,可分别通过
@ObjCSelector与@SwiftFunction注解利用。
HostApi 定义示例
来自示例文件 messages.dart:
enum Code { one, two } class MessageData { MessageData({required this.code, required this.data}); String? name; String? messageDescription; Code code; Map<String, String> data; } @HostApi() abstract class ExampleHostApi { String getHostLanguage(); // 这两个注解让 ObjC 与 Swift 中的方法命名更符合各自语言习惯。 @ObjCSelector('addNumber:toNumber:') @SwiftFunction('add(_:to:)') int add(int a, int b); @async bool sendMessage(MessageData message); }可见:枚举、自定义数据类(含可空字段、嵌套类型Map<String, String>)与带注解的异步方法都能在定义文件中直接表达。
各平台接入步骤
Flutter 调用 iOS
- 将生成的 Objective-C 或 Swift 代码加入 Xcode 工程参与编译(例如
ios/Runner.xcworkspace或.podspec)。 - 实现生成的 protocol(协议)以处理调用,并将其设置为消息的 handler。
Flutter 调用 Android
- 将生成的 Java 或 Kotlin 代码加入
./android/app/src/main/java目录参与编译。 - 实现生成的 Java 或 Kotlin 接口以处理调用,并将其设置为消息的 handler。
Flutter 调用 Windows
- 将生成的 C++ 代码加入
./windows目录,并加入windows/CMakeLists.txt。 - 实现生成的 C++ 抽象类以处理调用,并将其设置为消息的 handler。
Flutter 调用 macOS
- 将生成的 Objective-C 或 Swift 代码加入 Xcode 工程参与编译(例如
macos/Runner.xcworkspace或.podspec)。 - 实现生成的 protocol(协议)以处理调用,并将其设置为消息的 handler。
Flutter 调用 Linux
- 将生成的 GObject 代码加入
./linux目录,并加入linux/CMakeLists.txt。 - 实现生成的 protocol,并将其设置为 API 对象的 vtable(虚函数表)。
从宿主平台反向调用 Flutter
Pigeon 同样支持反向调用:使用@FlutterApi()标注那些"实现在 Flutter、但由宿主平台发起调用"的 API,接入步骤与正向调用类似、方向相反,详见 FlutterApi Example。
多语言实现与调用示例
结合示例应用的生成代码(位于 example/app 下,如 Messages.g.swift、Messages.g.kt、messages.g.cpp 与 messages.g.cc),可以看到各语言侧的实现形态:
Dart 侧调用 HostApi
final ExampleHostApi _api = ExampleHostApi(); /// 调用宿主方法 `add` 并传入参数。 Future<int> add(int a, int b) async { try { return await _api.add(a, b); } catch (e) { // 处理错误。 return 0; } } /// 通过 `MessageData` 类与 `sendMessage` 方法发送消息。 Future<bool> sendMessage(String messageText) { final message = MessageData( code: Code.one, data: <String, String>{'header': 'this is a header'}, messageDescription: 'uri text', ); try { return _api.sendMessage(message); } catch (e) { // 处理错误。 return Future<bool>(() => true); } }Swift 侧实现
注意:与其他语言不同,Swift 中抛错请使用PigeonError而不是FlutterError,因为FlutterError并不遵循Swift.Error协议:
private class PigeonApiImplementation: ExampleHostApi { func getHostLanguage() throws -> String { return "Swift" } func add(_ a: Int64, to b: Int64) throws -> Int64 { if a < 0 || b < 0 { throw PigeonError(code: "code", message: "message", details: "details") } return a + b } func sendMessage(message: MessageData) async throws -> Bool { if message.code == Code.one { throw PigeonError(code: "code", message: "message", details: "details") } return true } }Kotlin 侧实现
private class PigeonApiImplementation : ExampleHostApi { override fun getHostLanguage(): String { return "Kotlin" } override fun add(a: Long, b: Long): Long { if (a < 0L || b < 0L) { throw FlutterError("code", "message", "details") } return a + b } override suspend fun sendMessage(message: MessageData): Boolean { if (message.code == Code.ONE) { throw FlutterError("code", "message", "details") } return true } }注意@async在 Kotlin 侧生成为suspend函数,这正是 README 中"现代并发签名"的落地体现;错误通过抛FlutterError自动翻译为PlatformException。
C++ 侧实现
class PigeonApiImplementation : public ExampleHostApi { public: PigeonApiImplementation() {} virtual ~PigeonApiImplementation() {} ErrorOr<std::string> GetHostLanguage() override { return "C++"; } ErrorOr<int64_t> Add(int64_t a, int64_t b) { if (a < 0 || b < 0) { return FlutterError("code", "message", "details"); } return a + b; } void SendMessage(const MessageData& message, std::function<void(ErrorOr<bool> reply)> result) { if (message.code() == Code::kOne) { result(FlutterError("code", "message", "details")); return; } result(true); } };可见 C++ 侧同步方法直接返回ErrorOr<T>,异步方法通过std::function<void(ErrorOr<bool>)>回调返回结果——与 README 中"异步方法通过提供的回调返回FlutterError"的描述一一对应。
GObject 侧实现
GObject 侧将每个方法实现为 C 函数,并聚合到一个 vtable 中:
static PigeonExamplePackageExampleHostApiGetHostLanguageResponse* handle_get_host_language(gpointer user_data) { return pigeon_example_package_example_host_api_get_host_language_response_new( "C++"); } static PigeonExamplePackageExampleHostApiAddResponse* handle_add( int64_t a, int64_t b, gpointer user_data) { if (a < 0 || b < 0) { g_autoptr(FlValue) details = fl_value_new_string("details"); return pigeon_example_package_example_host_api_add_response_new_error( "code", "message", details); } return pigeon_example_package_example_host_api_add_response_new(a + b); } static void handle_send_message( PigeonExamplePackageMessageData* message, PigeonExamplePackageExampleHostApiResponseHandle* response_handle, gpointer user_data) { PigeonExamplePackageCode code = pigeon_example_package_message_data_get_code(message); if (code == PIGEON_EXAMPLE_PACKAGE_CODE_ONE) { g_autoptr(FlValue) details = fl_value_new_string("details"); pigeon_example_package_example_host_api_respond_error_send_message( response_handle, "code", "message", details); return; } pigeon_example_package_example_host_api_respond_send_message(response_handle, TRUE); } static PigeonExamplePackageExampleHostApiVTable example_host_api_vtable = { .get_host_language = handle_get_host_language, .add = handle_add, .send_message = handle_send_message};从源码结构看,Linux 侧通过"同步函数直接返回...Response*对象、异步函数通过ResponseHandle响应"的约定与 README 中的错误处理方式保持一致,且验证了"实现生成的 protocol 并设置为 vtable"的接入步骤。
事件通道(Event Channel)示例
事件通道仅 Swift、Kotlin 与 Dart 生成器支持。定义方式如下(见 event_channel_messages.dart):
@EventChannelApi() abstract class EventChannelMethods { PlatformEvent streamEvents(); }注意定义中没有Stream返回类型,只声明被流式传输的元素类型PlatformEvent。
生成的 Dart 代码会提供一个返回Stream的方法:
Stream<String> getEventStream() async* { final Stream<PlatformEvent> events = streamEvents(); await for (final PlatformEvent event in events) { switch (event) { case IntEvent(): final int intData = event.data; yield '$intData, '; case StringEvent(): final String stringData = event.data; yield '$stringData, '; } } }Swift 侧需要自定义StreamEventsStreamHandler子类,在onListen中保存PigeonEventSink,然后通过success(...)推送事件、endOfStream()结束流,并注册 handler:
class EventListener: StreamEventsStreamHandler { var eventSink: PigeonEventSink<PlatformEvent>? override func onListen(withArguments arguments: Any?, sink: PigeonEventSink<PlatformEvent>) { eventSink = sink } func onIntEvent(event: Int64) { if let eventSink = eventSink { eventSink.success(IntEvent(data: event)) } } func onStringEvent(event: String) { if let eventSink = eventSink { eventSink.success(StringEvent(data: event)) } } func onEventsDone() { eventSink?.endOfStream() eventSink = nil } } let eventListener = EventListener() StreamEventsStreamHandler.register(with: binaryMessenger, streamHandler: eventListener)Kotlin 侧的形态几乎一致:
class EventListener : StreamEventsStreamHandler() { private var eventSink: PigeonEventSink<PlatformEvent>? = null override fun onListen(p0: Any?, sink: PigeonEventSink<PlatformEvent>) { eventSink = sink } fun onIntEvent(event: Long) { eventSink?.success(IntEvent(data = event)) } fun onStringEvent(event: String) { eventSink?.success(StringEvent(data = event)) } fun onEventsDone() { eventSink?.endOfStream() eventSink = null } } val eventListener = EventListener() StreamEventsStreamHandler.register(flutterEngine.dartExecutor.binaryMessenger, eventListener)生成代码的稳定性与版本兼容性(重要警告)
Pigeon 的定位是替代插件与应用内部实现中直接使用 MethodChannel 的做法。由于 Pigeon 的预期用途是作为内部实现细节,其开发方向强烈偏向"改进生成代码"而非"与旧版生成代码保持兼容",因此生成代码的破坏性变更很常见。
- 不要在公开 API 中使用 Pigeon 生成的代码:官方明确"强烈不建议"这样做,因为一旦更新 Pigeon 版本导致生成代码变化,就会对你的客户端造成破坏性变更。
- 跨版本兼容性:Pigeon 通信所用的消息通道代码是内部实现细节,可能随时变化,通信层的变化不被视为破坏性变更。通信两端(Dart 代码与宿主语言代码)必须使用同一版本的 Pigeon 生成;使用不同版本生成的代码行为未定义,甚至可能导致应用崩溃。
- 不要把生成代码拆分到多个包:例如把生成的 Dart 代码放在 platform interface 包、把宿主语言代码放在 platform implementation 包,很可能在部分插件客户端更新后导致崩溃。
因此,Pigeon 生成代码应始终作为一个整体、由同一版本生成,并作为插件内部实现而非公开 API 暴露。
从源码看 Pigeon 的架构
Pigeon 的仓库结构可以直接印证其"一份定义、多语言生成"的设计:
- lib/pigeon.dart 是公共 API 出口,统一导出
CppOptions、DartOptions、GObjectOptions、JavaOptions、KotlinOptions、ObjcOptions、SwiftOptions以及事件通道与代理 API 相关选项,还有核心的pigeon_lib。 - lib/src 下按语言分列了生成器:
cpp/、dart/、gobject/、java/、kotlin/、objc/、swift/,另含ast.dart(抽象语法树)、generator.dart、generator_tools.dart等基础设施。从源码结构可以推断,Pigeon 先把 Dart 定义文件解析为统一的 AST,再由各语言生成器分别产出目标代码,这正是"消除跨平台字符串管理"的底层保证。 - pigeons 目录存放 Pigeon 自身的测试定义文件(如 core_tests.dart、event_channel_tests.dart、native_interop_tests.dart 等),这些文件覆盖了枚举、可空字段、非空字段、可空返回、多参方法、代理 API 等边界场景,可作为编写复杂接口定义的参考。
- platform_tests 目录存放跨平台测试工程,用于验证生成的各语言代码在真实平台上的行为。
反馈与参与
如果在使用中发现问题,可以在 flutter/flutter 提交 issue,并在标题开头加上[pigeon]前缀。更多示例还可参考本仓库内的 video_player 插件等真实使用 Pigeon 的项目。
【免费下载链接】packagesA collection of useful packages maintained by the Flutter team项目地址: https://gitcode.com/GitHub_Trending/pac/packages
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考