news 2026/9/19 16:52:36

在 Flutter 应用中直接使用 Pigeon Native Interop:FFI 与 JNI 平台通信实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
在 Flutter 应用中直接使用 Pigeon Native Interop:FFI 与 JNI 平台通信实战指南

在 Flutter 应用中直接使用 Pigeon Native Interop:FFI 与 JNI 平台通信实战指南

【免费下载链接】packagesA collection of useful packages maintained by the Flutter team项目地址: https://gitcode.com/GitHub_Trending/pac/packages

Pigeon 是 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 的AppDelegate,Dart 代码绕过消息通道,通过 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/iOS)
lib/src/native_interop_example.g.jni.dart由 jnigen 生成的 JNI 绑定(Android)
android/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)中包含三个关键的运行时互操作库:jni(JNI 桥)、objective_c(Objective-C 桥)和ffi(Dart FFI 基础库);开发依赖则包含pigeon(本地路径引用仓库根)、jnigenffigenswiftgenswift2objc等生成工具。

定义 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: true+ffiModuleName: '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()函数,流程比表面看起来要复杂:

  1. 先生成普通示例:对example/app/pigeons/下的 messages 定义执行常规 Pigeon 生成。
  2. 编译原生应用:调用_compileNativeInteropExampleApp(),通过flutter build apk --config-only配置工程,然后执行 Gradle 任务:app:compileReleaseKotlin,产出build/app/tmp/kotlin-classes/release下的 Kotlin 编译产物。这一步在无 Android SDK 或 Java 环境时会自动跳过。
  3. 运行 Pigeon 生成:以pigeons/native_interop_example.dart为输入,产出 Dart(lib/src/native_interop_example.g.dart)、Kotlin(NativeInteropExample.g.kt)和 Swift(NativeInteropExample.g.swift)三类代码。
  4. 生成 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
  5. 自动格式化:脚本默认对所有生成输出执行dart format

值得注意的是各生成器自身也有平台前置条件:FFI 生成只支持 macOS 宿主环境(配置脚本中通过Platform.isMacOS检查);JNI 生成则要求可用 Java 运行时(通过JAVA_HOMEjava命令探测)。

原生端实现:在应用而非插件中注册

Android(Kotlin + JNI)

Android 侧的原生实现位于 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.dartgetInstance的实现)。

iOS / macOS(Swift + FFI)

Apple 平台侧的实现位于 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.hRunner.h等桥接头文件。

生成的原生代码形态

原生生成代码同样存在于仓库中可供对照:Android 为 NativeInteropExample.g.kt,iOS 为 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中包含两个编解码器:

  • _PigeonJniCodec(Android):在 Dart 基本类型与 JNI 对象间转换——intJLongdoubleJDoubleStringJStringUint8List/Int32List/Int64List/Float64ListJByteArray/JIntArray/JLongArray/JDoubleArrayList/Map则递归转换为JList/JMap;Kotlin 侧的Unit(无返回值)通过反射取得kotlin/Unit.INSTANCE静态字段来编码。
  • _PigeonFfiCodec(Apple):基于package:objective_c,在 Dart 值与 Foundation 对象间转换——NSNumber承载数值与布尔、NSString承载字符串、NSArray/NSDictionary承载容器、NSData承载字节数据。其中值得注意的两个细节:
    • 二进制类型通过PigeonTypedData包装(内部记录类型标签 0~4,分别对应Uint8List/Int32List/Int64List/Float32List/Float64List);
    • 泛型容器中的数值通过NumberWrapper包装类型信息(1=long、2=double、3=bool),以解决 ObjC 容器无法区分数值类型的问题。

这些代码由 Pigeon 自动生成,使用者无需手写,但理解其存在有助于排查跨语言数据转换问题。FFI 桥的生成范围还受native_interop_example_ffigen_config.dart中的白名单控制:只有NativeInteropExampleApiPigeonError等少数类与枚举会被纳入绑定,NS前缀的 Foundation 类型则交给package:objective_c处理。

集成测试验证

仓库为示例应用提供了端到端测试 integration_test/example_app_test.dart:

void 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 的适用边界:

  • 支持平台:Android(JNI)、iOS 与 macOS(FFI)。示例代码中通过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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 16:51:03

跨阻放大器设计实战:光电二极管检测电路从原理到PCB

简介&#xff1a;一份讲解光电二极管检测电路工作原理与设计方案的PDF资料&#xff0c;适合从事光检测电路设计、传感器前端模拟电路开发的工程师及电子相关专业学生。内容从基本组成入手&#xff0c;阐述光电二极管在零偏置方式下的电流产生机制、前置放大器将微弱电流转换为电…

作者头像 李华
网站建设 2026/9/19 16:47:51

算法分析实验指南:从理论复杂度到实测性能验证

简介&#xff1a;算法分析实验报告4.3以棋盘覆盖问题为载体&#xff0c;系统展示了分治算法的完整求解流程。内容涵盖实验目的、预习任务、伪代码设计、C语言实现、上机调试过程、实验结果分析以及时间复杂度分析&#xff0c;适合正在学习分治策略、需要参考实验报告或理解棋盘…

作者头像 李华
网站建设 2026/9/19 16:37:21

特殊字符完全指南:从Unicode编码到HTML实体与中文乱码排查

1. 为什么我们离不开特殊字符&#xff1a;从一次文档翻车事故说起先讲一件让我印象特别深的事。去年我帮朋友校对一份产品说明书&#xff0c;原稿里写的是"重量≤ 5kg&#xff0c;误差 0.1kg"。排版同事拿到稿子后&#xff0c;发现"≤"和""在Wor…

作者头像 李华
网站建设 2026/9/19 16:36:58

MATLAB数字信号处理仿真:采样率、滤波器与FFT参数设置及验证方法

简介&#xff1a;一份面向工程技术人员和在校学生的《数字信号处理MATLAB仿真》PDF文档&#xff0c;围绕数字信号处理中连续与离散两大主线&#xff0c;系统讲解如何在MATLAB环境下完成信号的表示、基本运算、时域分析和频域分析。实验内容从单位冲击信号、单位阶跃函数、斜坡函…

作者头像 李华
网站建设 2026/9/19 16:33:40

GmSSL 与 Nginx 国密双证书配置实战:TLCP 改造避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华