
在 Flutter 应用中直接使用 Pigeon Native InteropFFI 与 JNI 平台通信实战指南【免费下载链接】packagesA collection of useful packages maintained by the Flutter team项目地址: https://gitcode.com/GitHub_Trending/pac/packagesPigeon 是 Flutter 团队维护的代码生成工具传统上它通过 MethodChannel 在 Flutter 与原生代码之间通信。而 Native Interop原生互操作则是一条更底层的路径不经过消息通道直接在应用中通过 Dart FFI 调用 Objective-C/Swift、通过 JNI 调用 Kotlin/Java。本指南以 Flutter 官方仓库中packages/pigeon/example/native_interop_app示例应用为主线讲解如何在不编写插件的前提下让应用直接与宿主平台的原生代码通信并掌握代码生成、原生注册、Dart 调用与集成测试的完整闭环。Native Interop 解决了什么问题传统 Pigeon 生成的平台通道代码依赖 Flutter 引擎的消息路由机制Dart 端通过BasicMessageChannel发送经过编解码的消息原生端在插件注册器中注册 handler。整个过程工作良好但要求平台逻辑必须托管在插件Plugin中。Native Interop 特性改变了这一约束。正如示例应用的 README 所述它演示了直接在应用Application中使用 Pigeon 的 Native Interop 特性直接的 FFI 和 JNI 函数调用进行平台通信而非在插件中。也就是说你可以把原生实现直接写进 Android 的MainActivity或 iOS 的AppDelegateDart 代码绕过消息通道通过 FFI/JNI 的桥接代码直接调用到宿主函数。这一能力对于需要在应用内直接访问系统 API、硬件能力或第三方原生 SDK 的场景尤其有价值。从本仓库生成的桥接代码可以清楚看到两种路径的并存lib/src/native_interop_example.g.dart中同时导入了 FFI 桥native_interop_example.g.ffi.dart和 JNI 桥native_interop_example.g.jni.dart并在运行时按平台选择调用方式。示例应用的目录结构packages/pigeon/example/native_interop_app是一个完整的 Flutter 应用工程其关键组成部分如下目录/文件作用pigeons/native_interop_example.dartPigeon 输入定义API 声明与ConfigurePigeon互操作配置lib/main.dart应用入口演示如何在 Dart 侧选择 Native Interop 通道lib/src/native_interop_example.g.dartPigeon 生成的 Dart 桥接代码含 FFI/JNI 分派逻辑lib/src/native_interop_example.g.ffi.dart由 swiftgen ffigen 生成的 FFI 绑定macOS/iOSlib/src/native_interop_example.g.jni.dart由 jnigen 生成的 JNI 绑定Androidandroid/app/src/main/kotlin/dev/flutter/pigeonnativeinteropapp/Android 原生端MainActivity.kt注册实现NativeInteropExample.g.kt为生成代码ios/Runner/iOS 原生端AppDelegate.swift注册实现NativeInteropExample.g.swift为生成代码integration_test/example_app_test.dart端到端集成测试tool/pigeon/FFI/JNI 生成器ffigen、jnigen的独立配置脚本test_driver/integration_test.dart集成测试驱动入口工程依赖见 pubspec.yaml中包含三个关键的运行时互操作库jniJNI 桥、objective_cObjective-C 桥和ffiDart FFI 基础库开发依赖则包含pigeon本地路径引用仓库根、jnigen、ffigen、swiftgen、swift2objc等生成工具。定义 API 与 Native Interop 配置Pigeon 的输入文件 pigeons/native_interop_example.dart 是整个生成流程的源头。除了用HostApi()声明一个最简单的宿主 API 外关键在于ConfigurePigeon注解import package:pigeon/pigeon.dart; ConfigurePigeon( PigeonOptions( // 推荐编译后的应用目录路径即 pubspec.yaml 所在目录 appDirectory: ./, dartOptions: DartOptions(), kotlinOptions: KotlinOptions( useJni: true, // 可选搜索已编译本地类的路径主要用于独立应用场景 jniClassPaths: String[build/app/tmp/kotlin-classes/release], ), swiftOptions: SwiftOptions(useFfi: true, ffiModuleName: Runner), ), ) HostApi() abstract class NativeInteropExampleApi { void doSomething(); }这里的关键配置项及含义appDirectory: ./指向编译后的应用目录存放pubspec.yaml的位置。这是启用 Native Interop 生成的前提Pigeon 需要它来定位宿主应用的可执行产物。kotlinOptions.useJni: true为 Android 端启用 JNI 代码生成Dart 将通过 JNI 直接调用 Kotlin 实现。kotlinOptions.jniClassPaths可选参数指定用于搜索已编译本地 Kotlin 类的目录。示例中指向build/app/tmp/kotlin-classes/release这正是 Gradle 编译 release Kotlin 类后产出的目录。之所以需要它是因为独立应用非插件无法依赖插件注册机制Pigeon 需要直接解析这些类来生成 JNI 桥。swiftOptions.useFfi: trueffiModuleName: Runner为 Apple 平台iOS/macOS启用 FFI 生成ffiModuleName指向宿主应用的可执行模块名。在 iOS 应用中这个模块名通常是 Runner。API 本身极简——一个无参数、无返回值的doSomething()——但足以完整演示从 Dart 到 Kotlin/Swift 的调用闭环。重新生成代码一条命令全流程示例 README 给出了更新生成代码的命令cd ../.. dart tool/generate.dart从example/native_interop_app目录上移两级即到达 Pigeon 包根目录packages/pigeon然后在包根目录执行生成脚本。该脚本入口为 tool/generate.dart其实际生成逻辑位于 tool/shared/generation.dart 的generateExamplePigeons()函数流程比表面看起来要复杂先生成普通示例对example/app/pigeons/下的 messages 定义执行常规 Pigeon 生成。编译原生应用调用_compileNativeInteropExampleApp()通过flutter build apk --config-only配置工程然后执行 Gradle 任务:app:compileReleaseKotlin产出build/app/tmp/kotlin-classes/release下的 Kotlin 编译产物。这一步在无 Android SDK 或 Java 环境时会自动跳过。运行 Pigeon 生成以pigeons/native_interop_example.dart为输入产出 Dartlib/src/native_interop_example.g.dart、KotlinNativeInteropExample.g.kt和 SwiftNativeInteropExample.g.swift三类代码。生成 FFI/JNI 桥分别执行 native_interop_example_ffigen_config.dart基于 swiftgen/ffigen与 native_interop_example_jnigen_config.dart基于 jnigen生成lib/src/下的.g.ffi.dart与.g.jni.dart。自动格式化脚本默认对所有生成输出执行dart format。值得注意的是各生成器自身也有平台前置条件FFI 生成只支持 macOS 宿主环境配置脚本中通过Platform.isMacOS检查JNI 生成则要求可用 Java 运行时通过JAVA_HOME或java命令探测。原生端实现在应用而非插件中注册AndroidKotlin JNIAndroid 侧的原生实现位于 MainActivity.kt。它直接在FlutterActivity中实现 API 并通过生成器注册全程不涉及插件package dev.flutter.pigeonnativeinteropapp import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine private class PigeonApiImplementation : NativeInteropExampleApi { override fun doSomething() { // 真实应用中这里实现原生平台逻辑访问 Android 系统 API、 // 硬件特性或第三方原生 SDK println(NativeInteropExampleApi.doSomething called from Dart) } } class MainActivity : FlutterActivity() { override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) val api PigeonApiImplementation() NativeInteropExampleApiRegistrar().register(api) } }注册入口NativeInteropExampleApiRegistrar().register(api)是 Pigeon 为 Native Interop 生成的特殊类它不依赖 Flutter 的插件注册表而是把实现实例登记到 JNI 桥可查询到的位置Dart 端的 JNI 桥会通过NativeInteropExampleApiRegistrar().getInstance(...)直接取回该实例参见生成的native_interop_example.g.dart中getInstance的实现。iOS / macOSSwift FFIApple 平台侧的实现位于 AppDelegate.swift。应用代理遵循FlutterImplicitEngineDelegate协议在隐式 Flutter 引擎初始化时注册实现import Flutter import UIKit private class PigeonApiImplementation: NativeInteropExampleApi { func doSomething() throws { // 真实应用中这里实现原生平台逻辑访问 iOS 系统 API、 // 硬件特性或第三方原生框架 print(NativeInteropExampleApi.doSomething called from Dart) } } main objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) let api PigeonApiImplementation() NativeInteropExampleApiSetup.register(api: api) } }生成的NativeInteropExampleApiSetup.register(api:)把实现登记到 FFI 桥Dart 侧通过NativeInteropExampleApiSetup.getInstanceWithName(...)取回。FFI 绑定在生成时需要把 Swift 声明转换为 Objective-C 兼容接口ios/Runner_objc_gen/NativeInteropExample.g.m再经 ffigen 生成 Dart 侧绑定因此示例工程中可见Runner-Bridging-Header.h与Runner.h等桥接头文件。生成的原生代码形态原生生成代码同样存在于仓库中可供对照Android 为 NativeInteropExample.g.ktiOS 为 NativeInteropExample.g.swift。此外NativeInteropExample.kt 与 NativeInteropExample.swift 中还展示了异步 API 的两种等价写法回调风格与协程/async 风格供扩展示例 API 时参考。Dart 端调用按平台选择通信路径应用入口 lib/main.dart 展示了 Dart 侧的调用方式final NativeInteropExampleApi _api (Platform.isAndroid || Platform.isIOS || Platform.isMacOS) ? NativeInteropExampleApi.createWithNativeInteropApi() : NativeInteropExampleApi();逻辑非常直白Android / iOS / macOS调用createWithNativeInteropApi()工厂方法走 Native Interop 路径JNI 或 FFI其他平台如 Web、Windows、Linux回退到默认构造器NativeInteropExampleApi()使用传统 BasicMessageChannel 通道。随后在initState中调用_api.doSomething()成功与失败分别更新 UI 文本。失败分支捕获的是PlatformException——这说明 Native Interop 路径虽然底层是 FFI/JNI但对外仍然保持着与 MethodChannel 一致的异常语义。从生成的 native_interop_example.g.dart 可以看到工厂方法的内部机制createWithNativeInteropApi()先调用NativeInteropExampleApiForNativeInterop.getInstance()该方法按平台分派——Android 走 JNI 桥的NativeInteropExampleApiRegistrar().getInstance(...)iOS/macOS 走 FFI 桥的NativeInteropExampleApiSetup.getInstanceWithName(...)若平台不支持则抛出UnsupportedError并提示使用默认构造器。拿到桥接实例后doSomething()内部根据持有的是 JNI 还是 FFI 句柄选择对应调用路径并将原生异常统一包装为PlatformException抛出。底层编解码与数据转换Native Interop 之所以能替代消息通道是因为生成的代码内部实现了 Dart 与原生对象之间的直接转换。生成的native_interop_example.g.dart中包含两个编解码器_PigeonJniCodecAndroid在 Dart 基本类型与 JNI 对象间转换——int↔JLong、double↔JDouble、String↔JString、Uint8List/Int32List/Int64List/Float64List↔JByteArray/JIntArray/JLongArray/JDoubleArrayList/Map则递归转换为JList/JMapKotlin 侧的Unit无返回值通过反射取得kotlin/Unit.INSTANCE静态字段来编码。_PigeonFfiCodecApple基于package:objective_c在 Dart 值与 Foundation 对象间转换——NSNumber承载数值与布尔、NSString承载字符串、NSArray/NSDictionary承载容器、NSData承载字节数据。其中值得注意的两个细节二进制类型通过PigeonTypedData包装内部记录类型标签 0~4分别对应Uint8List/Int32List/Int64List/Float32List/Float64List泛型容器中的数值通过NumberWrapper包装类型信息1long、2double、3bool以解决 ObjC 容器无法区分数值类型的问题。这些代码由 Pigeon 自动生成使用者无需手写但理解其存在有助于排查跨语言数据转换问题。FFI 桥的生成范围还受native_interop_example_ffigen_config.dart中的白名单控制只有NativeInteropExampleApi、PigeonError等少数类与枚举会被纳入绑定NS前缀的 Foundation 类型则交给package:objective_c处理。集成测试验证仓库为示例应用提供了端到端测试 integration_test/example_app_test.dartvoid main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets(gets host language, (WidgetTester tester) async { await tester.pumpWidget(const MyApp()); await tester.pumpAndSettle(); expect(find.textContaining(Called doSomething() successfully!), findsOneWidget); }); }测试通过IntegrationTestWidgetsFlutterBinding在真实设备或模拟器上启动应用断言 Dart 调用doSomething()后成功文本出现从而验证 JNI/FFI 通道真实打通。配合 test_driver/integration_test.dart 驱动可通过flutter drive在目标平台上运行。由于三种平台Android/iOS/macOS共用同一测试逻辑该测试天然覆盖了 JNI 与 FFI 两条调用链。平台支持与适用前提综合示例应用代码与生成逻辑可以归纳出 Native Interop 的适用边界支持平台AndroidJNI、iOS 与 macOSFFI。示例代码中通过Platform.isAndroid || Platform.isIOS || Platform.isMacOS判断。生成环境FFI 绑定生成仅能在 macOS 上执行依赖 Xcode SDK 与 swiftgen/ffigen 工具链JNI 绑定生成需要 Java 运行时与已编译的 Android Kotlin 类产物。前置构建Pigeon 生成 JNI 桥前需要应用先完成 Kotlin 编译./gradlew :app:compileReleaseKotlin因此首次生成前往往需要先执行一次flutter build apk类的构建以产出build/app/tmp/kotlin-classes/release。其他平台回退在不支持 Native Interop 的平台上示例展示了回退到传统消息通道的兼容写法默认构造器保证 API 面一致。小结通过packages/pigeon/example/native_interop_app这个最小可运行示例可以完整掌握 Pigeon Native Interop 的工程闭环在ConfigurePigeon中开启useJni/useFfi并指定应用目录与模块名用dart tool/generate.dart一键产出 Dart/Kotlin/Swift 与 FFI/JNI 桥接代码在MainActivity/AppDelegate中直接注册实现最终在 Dart 侧通过createWithNativeInteropApi()完成调用。相比传统插件通道这一方案让原生代码可以直接存在于应用本体中同时仍保留PlatformException等一致的错误语义是应用级平台通信的实用选择。若要深入探索可进一步阅读仓库中 Pigeon 的 Native Interop 迁移指南 与 原生互操作指南了解从插件迁移至该方案的完整路径。【免费下载链接】packagesA collection of useful packages maintained by the Flutter team项目地址: https://gitcode.com/GitHub_Trending/pac/packages创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考