
在 Flutter 的实际开发过程中,单靠 Dart 层往往无法满足所有业务需求,尤其是当涉及到底层系统能力、第三方原生 SDK、硬件访问或平台特有功能时(如相机、蓝牙、权限管理等),就需要与原生平台进行通信。Flutter 提供了多种与原生端交互的机制,MethodChannel 、EventChannel 、BasicMessageChannel,允许在 Dart 层与原生代码之间高效地传递数据和调用方法。
本文将通过一个完整的 Demo,详细介绍这三种 Channel 的使用方法和最佳实践。
MethodChannel
MethodChannel
是 Flutter 与原生通信中最常用的一种方式,主要用于实现 Dart 层对原生方法的调用 ,以及原生端主动调用 Dart 层的方法。它采用异步消息传递机制,非常适合一次性的请求-响应式交互。
核心应用场景
- Dart 调用原生 :
- 获取设备信息(如电池电量、网络状态、设备型号)
- 调用原生 SDK 功能(如地图、支付、分享)
- 触发平台特定操作(如打开系统设置、显示原生弹窗)
- 原生调用 Dart :
- 将原生页面的操作结果通知给 Dart
- 在原生端完成某个任务后,更新 Flutter UI
实现步骤
1. 初始化 MethodChannel
在 Dart 和原生端都需要创建一个具有相同名称的 MethodChannel
实例。
Dart 端 ( lib/main.dart
)
dart
final MethodChannel _methodChannel = MethodChannel("method_channel");
iOS 端 ( ios/Runner/AppDelegate.swift
)
swift
let methodChannel = FlutterMethodChannel(name: "method_channel", binaryMessenger: controller.binaryMessenger)
methodChannel.setMethodCallHandler(methodCallHandler)
2. Dart 调用原生
获取设备电量
dart
Future<void> _getBatteryLevel() async {
final int batteryLevel = await _methodChannel.invokeMethod("getBatteryLevel");
setState(() {
_batteryLevel = batteryLevel;
});
}
打开原生页面
dart
Future<void> _openNativePage() async {
await _methodChannel.invokeMethod("openNativePage");
}
iOS 端实现
swift
// AppDelegate.swift
private func methodCallHandler(call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "getBatteryLevel": getBatteryLevel(result)
case "openNativePage": openNativePage(result)
// ... 其他方法 ...
default: result(FlutterMethodNotImplemented)
}
}
// 获取电量
private func getBatteryLevel(_ result: @escaping FlutterResult) {
let device = UIDevice.current
device.isBatteryMonitoringEnabled = true
#if targetEnvironment(simulator)
result(100)
#else
if device.batteryState == UIDevice.BatteryState.unknown {
result(FlutterError(
code: "Error", message: "BatteryState is unkonw", details: nil
))
} else {
let level = Int(device.batteryLevel * 100)
result(level)
}
#endif
}
// 打开原生页面
private func openNativePage(_ result: @escaping FlutterResult) {
DispatchQueue.main.async {
guard let flutterViewController = self.flutterViewController else {
result(
FlutterError(
code: "ERROR",
message: "FlutterViewController is not initialized",
details: nil
)
)
return
}
let nativePage = NativePage()
let navigationController = UINavigationController(rootViewController: nativePage)
navigationController.modalPresentationStyle = .fullScreen
flutterViewController.present(navigationController, animated: true)
result(nil)
}
}
3. 原生调用 Dart
从原生页面更新 Flutter 计数器
iOS 端 ( ios/Runner/NativePage.swift
)
swift
private func increment() {
count += 1
// 从单例中获取 channel 实例
guard let methodChannel = ChannelManager.shared.methodChannel else {
return
}
// 调用 Dart 方法并传递参数
methodChannel.invokeMethod("setFlutterCount", arguments: ["count": count])
}
Dart 端接收
dart
void _setupChannels() {
_methodChannel.setMethodCallHandler(methodCallHandler);
// ...
}
Future<void> methodCallHandler(MethodCall call) async {
switch (call.method) {
case "setFlutterCount":
_setFlutterCount(call);
break;
}
}
void _setFlutterCount(MethodCall call) {
setState(() {
_count = call.arguments['count'] as int;
});
}
EventChannel
EventChannel
专门用于 原生端向 Dart 端发送持续的数据流。它就像一个数据管道,一旦建立连接,原生端可以随时向 Dart 端发送事件,直到连接被关闭。
核心应用场景
- 传感器数据:如加速度计、陀螺仪、GPS 位置更新
- 实时数据流:如网络状态变化、下载进度、蓝牙设备扫描结果
- 定时器和计时器:原生端定时器触发,持续向 Flutter 发送更新事件
实现步骤
1. 初始化 EventChannel
Dart 端 ( lib/main.dart
)
dart
final EventChannel _eventChannel = EventChannel("event_channel");
iOS 端 ( ios/Runner/AppDelegate.swift
)
swift
// event sink
var timerEventSink: FlutterEventSink?
let eventChannel = FlutterEventChannel(name: "event_channel", binaryMessenger: controller.binaryMessenger)
eventChannel.setStreamHandler(self)
2. Dart 端监听事件流
dart
void _setupChannels() {
// ...
// 监听名为 "timer" 的事件流
_eventChannel.receiveBroadcastStream("timer").listen(timerEvent);
}
void timerEvent(dynamic event) {
setState(() {
_timeValue = event.toString();
});
}
3. iOS 端实现 FlutterStreamHandler
swift
// AppDelegate.swift
extension AppDelegate: FlutterStreamHandler {
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
guard let args = arguments as? String else {
return FlutterError(code: "INVALID_ARGUMENTS", message: "Arguments must be a string", details: nil)
}
switch args {
case "timer":
timerEventSink = events
default:
return FlutterError(code: "UNKNOWN_STREAM", message: "Unknown stream: \(args)", details: nil)
}
return nil
}
func onCancel(withArguments arguments: Any?) -> FlutterError? {
if let args = arguments as? String {
switch args {
case "timer":
timerEventSink = nil
default:
break
}
}
return nil
}
}
4. 原生端发送事件
swift
private func startTimer(_ result: @escaping FlutterResult) {
startTimer = Date()
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { _ in
if let startTimer = self.startTimer {
let elapsed = Date().timeIntervalSince(startTimer)
// ... 计算时间 ...
let timeString = String(format: "%02d:%02d:%02d", hours, minutes, seconds)
// 通过 event sink 发送事件
self.timerEventSink?(timeString)
}
})
result(nil)
}
BasicMessageChannel
BasicMessageChannel
提供了一个非常灵活的双向消息通道,它使用 Codec(编解码器)来序列化和反序列化消息,支持传输字符串、数字、布尔值、列表、字典等复杂数据结构。
核心应用场景
- 传输复杂数据:当需要传递自定义对象或嵌套的 JSON 数据时
- 双向通信:在一次交互中,既需要发送数据,又需要接收回复
- 高性能数据传输 :可以使用二进制编解码器(如
BinaryCodec
)来提高性能
实现步骤
1. 初始化 BasicMessageChannel
Dart 端 ( lib/main.dart
)
dart
final BasicMessageChannel _basicMessageChannel = BasicMessageChannel(
"basic_message_channel",
JSONMessageCodec(), // 使用 JSON 编解码器
);
iOS 端 ( ios/Runner/AppDelegate.swift
)
swift
let basicMessageChannel = FlutterBasicMessageChannel(
name: "basic_message_channel",
binaryMessenger: controller.binaryMessenger,
codec: FlutterJSONMessageCodec.sharedInstance()
)
basicMessageChannel.setMessageHandler(basicMessageHandler)
2. Dart 端发送和接收消息
发送消息
dart
Future<void> _sendUserInfoToNative() async {
Map<String, dynamic> userInfo = {
"name": "John",
"age": 20,
"email": "john@example.com",
};
try {
final result = await _basicMessageChannel.send(userInfo);
setState(() {
_basicMessageInfo = Map<String, dynamic>.from(result);
});
} catch (e) {
print("Error sending user info to native: $e");
}
}
接收消息
dart
void _setupChannels() {
// ...
_basicMessageChannel.setMessageHandler(basicMessageHandler);
}
Future<void> basicMessageHandler(dynamic message) async {
setState(() {
_basicMessageInfo = Map<String, dynamic>.from(message);
});
return Future.value(message); // 返回确认消息
}
3. iOS 端接收和回复消息
接收消息
swift
private func basicMessageHandler(message: Any?, reply: FlutterReply) {
guard let messageDict = message as? [String: Any] else {
reply(FlutterError(...))
return
}
// 模拟处理后,返回一个包含更多信息的新字典
let response: [String: Any] = [
"received": true,
"receivedData": messageDict,
"platform": "iOS"
]
reply(response)
}
从原生页面发送消息
swift
// NativePage.swift
private func sendComplexDataToFlutter() {
let complexData: [String: Any] = [
"fromNative": true,
"timestamp": ISO8601DateFormatter().string(from: Date()),
// ...
]
ChannelManager.shared.basicMessageChannel?.sendMessage(complexData) { _ in
status = "yes"
}
}
总结与建议
Channel 类型 | 主要用途 | 数据流向 | 适用场景 |
---|---|---|---|
MethodChannel | 方法调用 | 双向(请求/响应) | 获取数据、触发动作、简单的同步/异步任务 |
EventChannel | 事件流 | 单向(原生 -> Dart) | 传感器数据、实时更新、持续的状态变化 |
BasicMessageChannel | 复杂数据交换 | 双向(消息/回复) | 自定义数据结构、文件传输、复杂的双向通信 |
- 优先选择
MethodChannel
:对于大多数常见的交互场景,MethodChannel
已经足够强大且易于使用。 - 使用
EventChannel
处理持续数据 :当需要从原生端持续接收数据时,EventChannel
是最佳选择,它避免了轮询,提高了效率。 BasicMessageChannel
用于复杂场景 :当MethodChannel
的参数和返回值无法满足你的数据结构需求时,或者需要更灵活的双向通信时,可以考虑使用BasicMessageChannel
。
通过合理地选择和使用这三种 Channel,可以轻松地在 Flutter 和原生平台之间构建起高效、稳定的通信桥梁,从而充分发挥 Flutter 的跨平台优势,同时利用原生平台的强大能力。
Demo演示

完整Demo代码
- main.dart
dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final MethodChannel _methodChannel = MethodChannel("method_channel");
final EventChannel _eventChannel = EventChannel("event_channel");
final BasicMessageChannel _basicMessageChannel = BasicMessageChannel(
"basic_message_channel",
JSONMessageCodec(),
);
int _batteryLevel = 0;
int _count = 0;
String _timeValue = "00:00:00";
Map<String, dynamic> _basicMessageInfo = {};
StreamSubscription? _eventSubscription;
void _setupChannels() {
_methodChannel.setMethodCallHandler(methodCallHandler);
_eventChannel.receiveBroadcastStream("timer").listen(timerEvent);
_basicMessageChannel.setMessageHandler(basicMessageHandler);
}
Future<void> methodCallHandler(MethodCall call) async {
switch (call.method) {
// 接受来自原生端的方法
case "setFlutterCount":
_setFlutterCount(call);
break;
}
}
void _setFlutterCount(MethodCall call) {
setState(() {
_count = call.arguments['count'] as int;
});
}
void timerEvent(dynamic event) {
setState(() {
_timeValue = event.toString();
});
}
Future<void> basicMessageHandler(dynamic message) async {
setState(() {
_basicMessageInfo = Map<String, dynamic>.from(message);
});
return Future.value(message);
}
Future<void> _getBatteryLevel() async {
final int batteryLevel = await _methodChannel.invokeMethod(
"getBatteryLevel",
);
setState(() {
_batteryLevel = batteryLevel;
});
}
Future<void> _openNativePage() async {
await _methodChannel.invokeMethod("openNativePage");
}
Future<void> _startTimer() async {
await _methodChannel.invokeMethod("startTimer");
}
Future<void> _stopTimer() async {
await _methodChannel.invokeMethod("stopTimer");
}
Future<void> _sendUserInfoToNative() async {
Map<String, dynamic> userInfo = {
"name": "John",
"age": 20,
"email": "john@example.com",
};
try {
final result = await _basicMessageChannel.send(userInfo);
setState(() {
_basicMessageInfo = Map<String, dynamic>.from(result);
});
} catch (e) {
print("Error sending user info to native: $e");
}
}
@override
void initState() {
super.initState();
_setupChannels();
_getBatteryLevel();
}
@override
void dispose() {
super.dispose();
_eventSubscription?.cancel();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Count: $_count',
style: Theme.of(context).textTheme.headlineMedium,
),
SizedBox(height: 20),
const Text('Device Battery:'),
Text(
'$_batteryLevel',
style: Theme.of(context).textTheme.headlineMedium,
),
SizedBox(height: 20),
Text(
'Time: $_timeValue',
style: Theme.of(context).textTheme.bodyLarge,
),
SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: _startTimer,
child: Text('Start Timer'),
),
SizedBox(width: 20),
ElevatedButton(
onPressed: _stopTimer,
child: Text('Stop Timer'),
),
],
),
SizedBox(height: 10),
Text('User Info:'),
Text(
_basicMessageInfo.toString(),
style: Theme.of(context).textTheme.bodyLarge,
),
ElevatedButton(
onPressed: _sendUserInfoToNative,
child: Text('Send User Info to Native'),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _openNativePage,
tooltip: 'Increment',
child: const Icon(Icons.navigation_rounded),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
- AppDelegate.swift
swift
import Flutter
import UIKit
// MARK: ChannelManager
public class ChannelManager {
static let shared = ChannelManager()
var methodChannel: FlutterMethodChannel?
var basicMessageChannel: FlutterBasicMessageChannel?
var eventChannel: FlutterEventChannel?
private init() {}
}
// MARK: AppDelegate
@main
@available(iOS 13.0, *)
@objc class AppDelegate: FlutterAppDelegate {
var flutterViewController: FlutterViewController?
// event sink
var timerEventSink: FlutterEventSink?
// timer
var timer: Timer?
var startTimer: Date?
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
GeneratedPluginRegistrant.register(with: self)
// 获取FlutteViewController
guard let flutterViewController = window?.rootViewController as? FlutterViewController else {
fatalError("RootViewController is not FlutterViewController")
}
self.flutterViewController = flutterViewController
// 设置Channe通道
setupChannel(with: flutterViewController)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
// 设置Channel通道
private func setupChannel(with controller: FlutterViewController) {
let methodChannel = FlutterMethodChannel(name: "method_channel", binaryMessenger: controller.binaryMessenger)
let eventChannel = FlutterEventChannel(name: "event_channel", binaryMessenger: controller.binaryMessenger)
let basicMessageChannel = FlutterBasicMessageChannel(
name: "basic_message_channel",
binaryMessenger: controller.binaryMessenger,
codec: FlutterJSONMessageCodec.sharedInstance()
)
ChannelManager.shared.methodChannel = methodChannel
ChannelManager.shared.eventChannel = eventChannel
ChannelManager.shared.basicMessageChannel = basicMessageChannel
methodChannel.setMethodCallHandler(methodCallHandler)
eventChannel.setStreamHandler(self)
basicMessageChannel.setMessageHandler(basicMessageHandler)
}
}
// MARK: Handler
@available(iOS 13.0, *)
extension AppDelegate: FlutterStreamHandler {
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
guard let args = arguments as? String else {
return FlutterError(code: "INVALID_ARGUMENTS", message: "Arguments must be a string", details: nil)
}
switch args {
case "timer":
timerEventSink = events
default:
return FlutterError(code: "UNKNOWN_STREAM", message: "Unknown stream: \(args)", details: nil)
}
return nil
}
func onCancel(withArguments arguments: Any?) -> FlutterError? {
if let args = arguments as? String {
switch args {
case "timer":
timerEventSink = nil
default:
break
}
}
return nil
}
private func methodCallHandler(call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "getBatteryLevel": getBatteryLevel(result)
case "openNativePage": openNativePage(result)
case "startTimer": startTimer(result)
case "stopTimer": stopTimer(result)
default: result(FlutterMethodNotImplemented)
}
}
private func basicMessageHandler(message: Any?, reply: FlutterReply) {
guard let messageDict = message as? [String: Any] else {
reply(FlutterError(code: "INVALID_MESSAGE", message: "Invalid message format", details: nil))
return
}
let response: [String: Any] = [
"received": true,
"receivedData": messageDict
]
reply(response)
}
}
// MARK: Founction
@available(iOS 13.0, *)
extension AppDelegate {
private func getBatteryLevel(_ result: @escaping FlutterResult) {
let device = UIDevice.current
device.isBatteryMonitoringEnabled = true
#if targetEnvironment(simulator)
result(100)
#else
if device.batteryState == UIDevice.BatteryState.unknown {
result(FlutterError(
code: "Error", message: "BatteryState is unkonw", details: nil
))
} else {
let level = Int(device.batteryLevel * 100)
result(level)
}
#endif
}
private func openNativePage(_ result: @escaping FlutterResult) {
DispatchQueue.main.async {
guard let flutterViewController = self.flutterViewController else {
result(
FlutterError(
code: "ERROR",
message: "FlutterViewController is not initialized",
details: nil
)
)
return
}
let nativePage = NativePage()
let navigationController = UINavigationController(rootViewController: nativePage)
navigationController.modalPresentationStyle = .fullScreen
flutterViewController.present(navigationController, animated: true)
result(nil)
}
}
private func startTimer(_ result: @escaping FlutterResult) {
startTimer = Date()
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { _ in
if let startTimer = self.startTimer {
let elapsed = Date().timeIntervalSince(startTimer)
let hours = Int(elapsed) / 3600
let minutes = (Int(elapsed) % 3600) / 60
let seconds = Int(elapsed) % 60
let timeString = String(format: "%02d:%02d:%02d", hours, minutes, seconds)
self.timerEventSink?(timeString)
}
})
result(nil)
}
private func stopTimer(_ result: @escaping FlutterResult) {
timer?.invalidate()
timer = nil
startTimer = nil
result(nil)
}
}
- NativePage.swift
swift
import SwiftUI
import UIKit
@available(iOS 13.0, *)
class NativePage: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
title = "Native Page"
setNavigation()
setupView()
}
@objc private func dismissPage() {
dismiss(animated: true)
}
private func setNavigation() {
navigationItem.leftBarButtonItem = UIBarButtonItem(
barButtonSystemItem: .close,
target: self,
action: #selector(dismissPage)
)
}
private func setupView() {
let swiftUIView = NativeSwiftUIView()
let hostingController = UIHostingController(rootView: swiftUIView)
addChild(hostingController)
view.addSubview(hostingController.view)
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
hostingController.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
hostingController.didMove(toParent:self)
}
}
@available(iOS 13.0, *)
struct NativeSwiftUIView: View {
@State private var count: Int = 0
@State private var status: String = "no"
var body: some View {
VStack(spacing: 15) {
Text("Native Page")
.font(.title.bold())
Text("Count: \(count)")
.font(.headline)
Button {
increment()
} label: {
Text("Increment")
.foregroundColor(.white)
.padding(.horizontal, 20)
.padding(.vertical, 8)
.background(
RoundedRectangle(
cornerRadius: 20,
style: .continuous
)
.fill(Color(.systemBlue))
)
}
Text("BasicMessageSendStatus: \(status)")
Button {
sendComplexDataToFlutter()
} label: {
Text("Send Complex Data")
.foregroundColor(.white)
.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.fill(Color.green)
)
}
}
}
private func increment() {
count += 1
guard let methodChannel = ChannelManager.shared.methodChannel else {
return
}
methodChannel.invokeMethod("setFlutterCount", arguments: ["count": count])
}
private func sendComplexDataToFlutter() {
let complexData: [String: Any] = [
"fromNative": true,
"timestamp": ISO8601DateFormatter().string(from: Date()),
"deviceInfo": [
"model": UIDevice.current.model,
"systemVersion": UIDevice.current.systemVersion,
"name": UIDevice.current.name
],
"randomData": Array(1...5).map { _ in Int.random(in: 1...100) }
]
ChannelManager.shared.basicMessageChannel?.sendMessage(complexData) { _ in
status = "yes"
}
}
}