由浅入深,从基本概念到源码解析,全面掌握 Flutter 与 Android/iOS/鸿蒙、Electron 与 Mac/Windows 的原生交互能力
一、为什么需要原生交互? 1.1 跨平台框架的局限 跨平台框架(Flutter、Electron、React Native 等)为开发效率带来巨大提升,但受限于自身运行时,无法直接访问所有原生能力:
场景
Flutter 移动端
Electron 桌面端
硬件访问
相机、蓝牙、NFC、传感器
串口、USB、显卡驱动
系统 API
推送、定位、生物识别
剪贴板、系统托盘、原生菜单
第三方 SDK
微信/支付宝支付、地图
企业认证、硬件加密狗
性能敏感
视频编解码、图像处理
大文件加解密、实时音视频
平台特性
iOS Live Activities、Android WorkManager
macOS Touch Bar、Windows 通知中心
核心矛盾 :跨平台代码运行在「沙箱」中,必须通过桥接层与原生世界通信。
1.2 两种典型架构对比 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ┌─────────────────────────────────────────────────────────────────────────┐ │ Flutter 移动端(Android / iOS / 鸿蒙) │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ Dart (UI/逻辑) ──► Platform Channel ──► Native (Kotlin/Swift/ArkTS) │ │ MethodChannel EventChannel Android/iOS/HarmonyOS API │ │ BasicMessageChannel │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────────┐ │ Electron 桌面端(Mac / Windows / Linux) │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ Renderer (HTML/JS) ──► IPC ──► Main Process ──► Native Addon │ │ contextBridge Node-API / FFI │ │ .node / .dll / .dylib │ │ │ └─────────────────────────────────────────────────────────────────────────┘
二、Flutter 与原生交互 2.1 基本概念 Flutter 的 Platform Channel 是 Dart 与原生代码之间的消息传递机制 ,底层基于 BinaryMessenger 进行异步二进制通信。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ┌──────────────────┐ channel name ┌──────────────────┐ │ │ ◄────────────────────────► │ │ │ Dart 端 │ ByteData (序列化消息) │ Native 端 │ │ (Client) │ Future/Stream 响应 │ (Host) │ │ │ │ │ │ MethodChannel │ │ MethodChannel │ │ EventChannel │ │ EventChannel │ │ BasicMessageChannel │ BasicMessageChannel │ └──────────────────┘ └──────────────────┘ │ │ │ StandardMessageCodec (JSON-like 序列化) │ │ 支持: null, bool, int, double, String │ │ Uint8List, List, Map │ ▼ ▼ Flutter Engine (C++) ──────────────────── Platform Embedder
2.1.2 三种 Channel 类型辨析
Channel 类型
通信模式
典型场景
特点
MethodChannel
请求-响应(RPC)
获取电池电量、调用支付 SDK、打开相机
最常用,一对一调用,返回 Future
EventChannel
流式数据(Stream)
监听传感器、位置更新、蓝牙扫描
单向流,Native → Dart,支持 cancel
BasicMessageChannel
简单消息传递
低层级自定义协议
无方法语义,纯消息收发,较少直接使用
2.1.3 数据类型映射(Dart ↔ Native)
Dart
Kotlin/Java
Swift/ObjC
ArkTS (鸿蒙)
null
null
nil
null
bool
Boolean
Bool / NSNumber
boolean
int (≤32位)
Int
Int32
number
int (>32位)
Long
Int64
number
double
Double
Double
number
String
String
String
string
Uint8List
ByteArray
FlutterStandardTypedData
Uint8Array
List
List
Array
Array
Map
HashMap
Dictionary
Object
2.2 MethodChannel 实战:获取电池电量 2.2.1 Dart 端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import 'package:flutter/services.dart' ;class _MyHomePageState extends State <MyHomePage > { static const platform = MethodChannel('samples.flutter.dev/battery' ); String _batteryLevel = 'Unknown' ; Future<void > _getBatteryLevel() async { try { final int level = await platform.invokeMethod<int >('getBatteryLevel' ); setState(() => _batteryLevel = 'Battery level: $level %' ); } on PlatformException catch (e) { setState(() => _batteryLevel = "Failed: '${e.message} '" ); } } @override Widget build(BuildContext context) { return Column( children: [ ElevatedButton(onPressed: _getBatteryLevel, child: Text('Get Battery' )), Text(_batteryLevel), ], ); } }
要点 :
Channel 名必须与原生端完全一致
invokeMethod 返回 Future,支持泛型
捕获 PlatformException 处理原生端 result.error()
2.2.2 Android (Kotlin) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import io.flutter.embedding.android.FlutterActivityimport io.flutter.embedding.engine.FlutterEngineimport io.flutter.plugin.common.MethodChannelclass MainActivity : FlutterActivity () { private val CHANNEL = "samples.flutter.dev/battery" override fun configureFlutterEngine (flutterEngine: FlutterEngine ) { super .configureFlutterEngine(flutterEngine) MethodChannel( flutterEngine.dartExecutor.binaryMessenger, CHANNEL ).setMethodCallHandler { call, result -> when (call.method) { "getBatteryLevel" -> { val level = getBatteryLevel() if (level != -1 ) { result.success(level) } else { result.error("UNAVAILABLE" , "Battery level not available." , null ) } } else -> result.notImplemented() } } } private fun getBatteryLevel () : Int { val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager return batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY) } }
2.2.3 iOS (Swift) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import Flutterimport UIKit@main @objc class AppDelegate : FlutterAppDelegate { override func application ( _ application : UIApplication , didFinishLaunchingWithOptions launchOptions : [UIApplication .LaunchOptionsKey : Any ]? ) -> Bool { let controller = window? .rootViewController as! FlutterViewController let channel = FlutterMethodChannel ( name: "samples.flutter.dev/battery" , binaryMessenger: controller.binaryMessenger ) channel.setMethodCallHandler { [weak self ] (call, result) in guard call.method == "getBatteryLevel" else { result(FlutterMethodNotImplemented ) return } self ? .receiveBatteryLevel(result: result) } return super .application(application, didFinishLaunchingWithOptions: launchOptions) } private func receiveBatteryLevel (result : FlutterResult ) { let device = UIDevice .current device.isBatteryMonitoringEnabled = true if device.batteryState == .unknown { result(FlutterError (code: "UNAVAILABLE" , message: "Battery level not available." , details: nil )) } else { result(Int (device.batteryLevel * 100 )) } } }
2.2.4 鸿蒙 (ArkTS) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import type { FlutterPlugin } from '@ohos/flutter' ;export default class EntryAbility extends UIAbility { onWindowStageCreate (windowStage : window .WindowStage ) { const flutterEngine = getFlutterEngine (); const messenger = flutterEngine.getBinaryMessenger (); const channel = new MethodChannel (messenger, 'samples.flutter.dev/battery' ); channel.setMethodCallHandler (async (call) => { if (call.method === 'getBatteryLevel' ) { const level = await this .getBatteryLevel (); return level; } throw new Error ('NotImplemented' ); }); } private async getBatteryLevel (): Promise <number > { const batteryInfo = await battery.getBatteryInfo (); return batteryInfo.batterySoc ; } }
2.3 EventChannel 实战:监听位置更新 适用于持续从 Native 向 Dart 推送数据 的场景。
2.3.1 Dart 端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import 'dart:async' ;import 'package:flutter/services.dart' ;class LocationService { static const _channel = EventChannel('samples.flutter.dev/location' ); Stream<Location> get locationStream => _channel .receiveBroadcastStream() .map((dynamic event) => Location.fromMap(Map <String , dynamic >.from(event as Map ))); } StreamSubscription? _subscription; void _startListening() { _subscription = LocationService().locationStream.listen( (loc) => print ('Lat: ${loc.lat} , Lng: ${loc.lng} ' ), onError: (e) => print ('Error: $e ' ), ); } void _stopListening() { _subscription?.cancel(); }
2.3.2 Android (Kotlin) - EventChannel.StreamHandler 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class LocationStreamHandler : EventChannel.StreamHandler { private var eventSink: EventChannel.EventSink? = null private var locationCallback: LocationCallback? = null override fun onListen (arguments: Any ?, events: EventChannel .EventSink ?) { eventSink = events locationCallback = object : LocationCallback() { override fun onLocationResult (result: LocationResult ) { for (loc in result.locations) { eventSink?.success(mapOf( "lat" to loc.latitude, "lng" to loc.longitude )) } } } fusedLocationClient.requestLocationUpdates(request, locationCallback!!, Looper.getMainLooper()) } override fun onCancel (arguments: Any ?) { locationCallback?.let { fusedLocationClient.removeLocationUpdates(it) } eventSink = null } } EventChannel(flutterEngine.dartExecutor.binaryMessenger, "samples.flutter.dev/location" ) .setStreamHandler(LocationStreamHandler())
2.4 底层原理:BinaryMessenger 所有 Channel 的底层都依赖 BinaryMessenger:
1 2 3 4 5 6 7 8 9 Dart 侧 (binary_messenger.dart) ├── send(channel, message) → Future<ByteData?> ├── setMessageHandler(channel, handler) └── 消息以 ByteData 形式序列化传输 Native 侧 (Android: DartExecutor, iOS: FlutterBinaryMessenger) ├── 实现 BinaryMessenger 接口 ├── 通过 Flutter Engine 与 Dart 隔离 └── 线程:Android 主线程,iOS 主线程
关键点 :
消息是异步 的,保证 UI 不阻塞
序列化由 StandardMessageCodec 完成(或自定义 MessageCodec)
Channel 名称是路由键,用于区分不同功能
手动维护 MethodChannel 容易出错(字符串拼写、类型不匹配)。Pigeon 是 Flutter 官方代码生成工具,从接口定义自动生成各端代码。
2.5.1 定义接口 (pigeon/api.dart) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import 'package:pigeon/pigeon.dart' ;@HostApi ()abstract class BatteryApi { int getBatteryLevel(); } @HostApi ()abstract class LocationApi { void startLocationUpdates(); void stopLocationUpdates(); } @FlutterApi ()abstract class LocationCallback { void onLocation(double lat, double lng); }
2.5.2 生成代码 1 2 3 dev_dependencies: pigeon: ^26.0.0
1 flutter pub run pigeon --input pigeon/api.dart
会生成:
dart/*.dart:Dart 端接口实现
kotlin/*.kt:Android Kotlin 实现骨架
swift/*.swift:iOS Swift 实现骨架
2.5.3 优势
对比
手动 MethodChannel
Pigeon
类型安全
运行时检查
编译期保证
维护成本
三端同步改
改接口定义即可
代码量
重复样板
自动生成
2.6 实际项目应用案例 案例 1:支付 SDK 接入 1 2 3 4 5 6 7 业务需求:Flutter 调起微信/支付宝支付,并接收支付结果回调 实现要点: 1. MethodChannel 调用 Native 调起支付 2. 支付结果通过 EventChannel 或 MethodChannel.invokeMethod 反向回调 3. Android: Activity Result API,iOS: URL Scheme / Universal Links 4. 注意:支付必须在主线程/主 Activity 完成
案例 2:相机自定义预览 + 拍照 1 2 3 4 5 6 7 业务需求:自定义相机 UI,支持滤镜、闪光灯、变焦 实现要点: 1. 使用 Platform View (AndroidView / UiKitView) 嵌入原生相机 View 2. MethodChannel 控制:开始预览、拍照、切换滤镜 3. EventChannel 或 Texture 传递预览帧(如需实时处理) 4. 注意线程:相机回调可能在子线程,需 Post 到主线程再通过 Channel 回传
案例 3:蓝牙设备扫描与连接 1 2 3 4 5 6 7 业务需求:扫描 BLE 设备,连接并读写特征值 实现要点: 1. EventChannel 持续推送扫描到的设备列表 2. MethodChannel 执行:连接、断开、读/写特征值 3. 连接状态、数据回调通过 EventChannel 流式回传 4. 鸿蒙侧使用 @ohos.bluetooth 相关 API,需申请权限
三、Electron 与原生交互 3.1 基本概念 3.1.1 Electron 架构回顾 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ┌─────────────────────────────────────────────────────────────────────┐ │ Renderer Process (多个) │ │ HTML + CSS + JavaScript (前端界面) │ │ 受沙箱限制,无法直接访问 Node.js / 原生模块 │ └───────────────────────────────────┬─────────────────────────────────┘ │ IPC (ipcRenderer / contextBridge) ┌───────────────────────────────────▼─────────────────────────────────┐ │ Main Process (单个) │ │ Node.js 运行时,可 require('fs')、require('原生模块') │ │ 可加载 .node (Native Addon)、调用 FFI │ └───────────────────────────────────┬─────────────────────────────────┘ │ Node-API / N-API ┌───────────────────────────────────▼─────────────────────────────────┐ │ Native Addon / 系统 API │ │ C/C++ 编写,编译为 .node (Unix) / .dll (Windows) │ │ 或通过 FFI 调用已有 .dll / .dylib / .so │ └─────────────────────────────────────────────────────────────────────┘
核心原则 :所有原生调用必须在 Main Process 完成,Renderer 通过 IPC 与 Main 通信。
3.1.2 两种原生交互方式
方式
适用场景
技术栈
Native Addon
自己用 C++ 写模块,编译成 .node
Node-API / N-API、node-gyp、node-addon-api
FFI
调用已有的 C 库(.dll/.dylib/.so)
node-ffi-napi、koffi
3.2 Native Addon 开发(Node-API) 3.2.1 环境准备 macOS :
Windows :
安装 Node.js 时勾选 “Tools for Native Modules”
或通过 npm install -g windows-build-tools 安装 VS Build Tools
依赖 :
1 2 npm install node-addon-api bindings npm install -g node-gyp
3.2.2 项目结构 1 2 3 4 5 6 native-addon/ ├── binding.gyp # 构建配置 ├── src/ │ └── addon.cc # C++ 源码 ├── package.json └── index.js # 对外导出
3.2.3 binding.gyp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 { "targets" : [ { "target_name" : "my_addon" , "sources" : [ "src/addon.cc" ] , "include_dirs" : [ "<!@(node -p \"require('node-addon-api').include\")" ] , "dependencies" : [ "<!(node -p \"require('node-addon-api').gyp\")" ] , "cflags!" : [ "-fno-exceptions" ] , "cflags_cc!" : [ "-fno-exceptions" ] , "defines" : [ "NAPI_DISABLE_CPP_EXCEPTIONS" ] , "conditions" : [ [ "OS=='win'" , { "msvs_settings" : { "VCCLCompilerTool" : { "ExceptionHandling" : 1 } } } ] ] } ] }
3.2.4 C++ 源码 (src/addon.cc) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <napi.h> Napi::Value Add (const Napi::CallbackInfo& info) { Napi::Env env = info.Env (); if (info.Length () < 2 || !info[0 ].IsNumber () || !info[1 ].IsNumber ()) { Napi::TypeError::New (env, "Number expected" ).ThrowAsJavaScriptException (); return env.Null (); } double a = info[0 ].As <Napi::Number>().DoubleValue (); double b = info[1 ].As <Napi::Number>().DoubleValue (); return Napi::Number::New (env, a + b); } Napi::Object Init (Napi::Env env, Napi::Object exports) { exports.Set ("add" , Napi::Function::New (env, Add)); return exports; } NODE_API_MODULE (my_addon, Init)
3.2.5 调用方 (index.js) 1 2 const addon = require ('bindings' )('my_addon' );console .log (addon.add (1.5 , 2.3 ));
3.2.6 在 Electron 中使用 关键 :Electron 的 Node.js 版本与官方 Node 不同,需要重新编译 Native Addon:
1 2 npm install @electron/rebuild --save-dev npx electron-rebuild
或在 package.json 中配置:
1 2 3 4 5 { "scripts" : { "rebuild" : "electron-rebuild" } }
Main Process 中 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const { app, BrowserWindow , ipcMain } = require ('electron' );const addon = require ('bindings' )('my_addon' );let win;function createWindow ( ) { win = new BrowserWindow ({ webPreferences : { nodeIntegration : false , contextIsolation : true } }); win.loadFile ('index.html' ); } ipcMain.handle ('add-numbers' , (event, a, b ) => { return addon.add (a, b); }); app.whenReady ().then (createWindow);
Preload (preload.js) :
1 2 3 4 5 const { contextBridge, ipcRenderer } = require ('electron' );contextBridge.exposeInMainWorld ('native' , { add : (a, b ) => ipcRenderer.invoke ('add-numbers' , a, b) });
Renderer :
1 2 const result = await window .native .add (1.5 , 2.3 );console .log (result);
3.3 FFI:调用已有 C 库 当需要调用已有的 .dll (Windows)、.dylib (macOS)、.so (Linux) 时,使用 FFI 无需重写 C++ 代码。
3.3.1 使用 koffi(推荐,支持 N-API)
1 2 3 4 5 6 7 8 9 10 11 12 13 const koffi = require ('koffi' );const libc = koffi.load ('libc.so.6' ); const ceil = libc.func ('double ceil(double)' , 'ceil' );console .log (ceil (3.2 )); const myLib = koffi.load ('./my_lib.dll' );const myFunc = myLib.func ('int my_func(const char* str, int len)' , 'my_func' );
3.3.2 使用 node-ffi-napi(注意 Electron 兼容性) 1 2 3 4 5 6 7 8 const ffi = require ('ffi-napi' );const ref = require ('ref-napi' );const msvcrt = ffi.Library ('msvcrt' , { 'ceil' : ['double' , ['double' ]] }); console .log (msvcrt.ceil (3.2 ));
注意 :Electron 20.3.8+ 的沙箱可能限制指针操作,导致 ffi-napi 崩溃,可考虑:
使用 koffi(基于 N-API,兼容性更好)
或通过 Main Process 调用,避免在 Renderer 使用 FFI
3.4 实际项目应用案例 案例 1:调用 Windows 系统 API(获取 CPU 使用率) 1 2 3 4 5 6 7 需求:在 Electron 应用中显示实时 CPU 使用率 实现: 1. 使用 node-ffi-napi 或 koffi 加载 kernel32.dll / pdh.dll 2. 调用 GetSystemTimes、PdhCollectQueryData 等 API 3. Main Process 定时采样,通过 IPC 推送到 Renderer 展示 4. 跨平台:macOS 使用 sysctl,Linux 使用 /proc/stat
案例 2:硬件加密狗认证 1 2 3 4 5 6 7 需求:桌面端软件需插入 USB 加密狗才能使用 实现: 1. 厂商提供 .dll / .so SDK 2. 通过 FFI 加载 SDK,调用 CheckDongle()、GetLicense() 等 3. Main Process 启动时校验,失败则禁止启动 4. 注意:.dll 路径需随应用打包正确配置(如 resources/ 目录)
案例 3:大文件加解密(性能敏感) 1 2 3 4 5 6 7 需求:对 GB 级文件进行 AES 加解密,纯 JS 太慢 实现: 1. 用 C++ 编写加解密模块,基于 OpenSSL 或 crypto 库 2. 编译为 Native Addon,暴露 encryptFile(path)、decryptFile(path) 3. Main Process 调用,通过 Stream 或进度回调推送到 Renderer 4. 可考虑 Worker 线程 + 原生模块,避免阻塞主进程
四、对比与选型 4.1 Flutter vs Electron 原生交互对比
维度
Flutter (移动端)
Electron (桌面端)
通信机制
Platform Channel(异步消息)
IPC + Native Addon / FFI
数据格式
StandardMessageCodec(JSON-like)
任意(JS 对象 ↔ C 结构体需手动处理)
类型安全
可配合 Pigeon 生成
需自行保证
多端一致性
同一套 Channel 名,各端实现不同
同一套 Addon/FFI 调用,各平台编译不同
性能
消息序列化有开销,适合中低频调用
Native Addon 直接调用,适合高性能场景
调试
可通过日志追踪 Channel 消息
需 gdb/lldb 调试 C++
4.2 何时用 MethodChannel / EventChannel / FFI / Native Addon
需求
推荐方案
单次调用原生 API(如获取电池)
Flutter MethodChannel / Electron IPC + Addon
持续接收原生数据流(如传感器)
Flutter EventChannel
调用已有 C 库,不想重写
Electron FFI
高性能计算、自定义算法
Native Addon(C++)
需要类型安全、少写样板
Flutter Pigeon
五、最佳实践与注意事项 5.1 Flutter
Channel 命名 :使用反向域名,如 com.yourapp.service/battery,避免冲突
主线程 :Android/iOS 的 Channel 回调一般在主线程,耗时操作应异步处理
错误处理 :原生端务必调用 result.error() 或 result.notImplemented(),避免 Dart 侧 Future 一直挂起
内存泄漏 :EventChannel 的 onCancel 必须移除监听器、释放资源
插件化 :可复用逻辑封装为 Flutter Plugin,发布到 pub.dev
5.2 Electron
安全 :禁用 Renderer 的 nodeIntegration,仅通过 contextBridge 暴露必要 API
重建 :每次升级 Electron 版本后执行 electron-rebuild
路径 :Native Addon 的 .node 文件路径在打包后可能变化,使用 __dirname、app.getPath('userData') 等正确处理
崩溃 :C++ 模块崩溃会导致整个进程退出,需做好 try-catch 和日志
5.3 鸿蒙特别说明
Flutter 对 OpenHarmony/HarmonyOS 的支持在快速演进,需关注官方文档和 flutter_harmony 等生态
ArkTS 与 Dart 的 MethodChannel 接口类似,但需使用鸿蒙提供的 FlutterPlugin、MethodChannel 等 API
权限、包名、签名等需符合鸿蒙应用规范
六、总结
技术栈
核心机制
典型用途
Flutter + Android/iOS/鸿蒙
Platform Channel(MethodChannel / EventChannel)
移动端调用相机、支付、蓝牙等
Electron + Mac/Windows
IPC + Native Addon / FFI
桌面端调用系统 API、硬件、高性能计算
掌握原生交互能力,是跨平台开发从「能写」到「写好」的关键一步。建议先跑通官方示例,再结合实际业务逐步封装和优化。
附录:参考资源