Flutter 与原生通信:MethodChannel 与 Pigeon 完全指南
Flutter 作为跨平台框架,需要频繁与原生平台(Android/iOS)进行通信。本文系统讲解
MethodChannel、EventChannel、BasicMessageChannel的使用场景和代码实现,并介绍Pigeon代码生成方案。
一、Flutter 与原生通信的三种方式
| 通信方式 | 方向 | 适用场景 | 数据类型 |
|---|---|---|---|
MethodChannel | Flutter → 原生(双向) | 调用原生功能(摄像头、蓝牙等) | 支持基础类型 + List/Map |
EventChannel | 原生 → Flutter(流式) | 监听原生事件流(传感器、音乐播放进度) | 支持基础类型 + List/Map |
BasicMessageChannel | 双向,任意时刻 | 高频通信、自定义协议 | ByteData(需自行序列化) |
二、MethodChannel 实战
2.1 Flutter 端代码
import 'package:flutter/services.dart';
class NativeApi {
static const _channel = MethodChannel('com.example.app/native');
// 调用原生方法(获取电池电量)
static Future<int> getBatteryLevel() async {
try {
final result = await _channel.invokeMethod<int>('getBatteryLevel');
return result ?? -1;
} on PlatformException catch (e) {
print('调用失败:${e.message}');
return -1;
}
}
// 传递复杂参数
static Future<String> compressImage(String imagePath, int quality) async {
try {
final result = await _channel.invokeMethod<String>(
'compressImage',
{
'path': imagePath,
'quality': quality,
},
);
return result ?? '';
} on PlatformException catch (e) {
print('压缩失败:${e.message}');
return '';
}
}
}
使用:
// 在 Widget 中调用
Future<void> _checkBattery() async {
final level = await NativeApi.getBatteryLevel();
setState(() {
_batteryLevel = level;
});
}
2.2 Android 端代码(Kotlin)
// MainActivity.kt
class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
"com.example.app/native"
).setMethodCallHandler { call, result ->
when (call.method) {
"getBatteryLevel" -> {
val batteryLevel = getBatteryLevel()
if (batteryLevel != -1) {
result.success(batteryLevel)
} else {
result.error("UNAVAILABLE", "电池电量不可用", null)
}
}
"compressImage" -> {
val path = call.argument<String>("path")
val quality = call.argument<Int>("quality") ?: 80
if (path != null) {
val compressedPath = compressImage(path, quality)
result.success(compressedPath)
} else {
result.error("INVALID_ARGUMENT", "路径不能为空", null)
}
}
else -> result.notImplemented()
}
}
}
private fun getBatteryLevel(): Int {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
} else {
val intent = Context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
val level = intent?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1
val scale = intent?.getIntExtra(BatteryManager.EXTRA_SCALE, -1) ?: -1
if (level == -1 || scale == -1) -1 else (level * 100 / scale.toFloat()).toInt()
}
}
}
2.3 iOS 端代码(Swift)
// AppDelegate.swift
import UIKit
import Flutter
@UIApplicationMain
@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: "com.example.app/native",
binaryMessenger: controller.binaryMessenger
)
channel.setMethodCallHandler { call, result in
switch call.method {
case "getBatteryLevel":
self.getBatteryLevel(result: result)
case "compressImage":
guard let args = call.arguments as? [String: Any],
let path = args["path"] as? String,
let quality = args["quality"] as? Int else {
result(FlutterError(code: "INVALID_ARGUMENT", message: "参数错误", details: nil))
return
}
self.compressImage(path: path, quality: quality, result: result)
default:
result(FlutterMethodNotImplemented)
}
}
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
private func getBatteryLevel(result: FlutterResult) {
let device = UIDevice.current
device.isBatteryMonitoringEnabled = true
let level = Int(device.batteryLevel * 100)
result(level)
}
}
三、EventChannel 实战(监听原生事件流)
3.1 Flutter 端代码
class NativeEventApi {
static const _eventChannel = EventChannel('com.example.app/events');
// 监听网络状态变化
static Stream<String> get networkStatusStream {
return _eventChannel
.receiveBroadcastStream()
.map((event) => event as String);
}
}
// 使用
StreamBuilder<String>(
stream: NativeEventApi.networkStatusStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text('网络状态:${snapshot.data}');
}
return const CircularProgressIndicator();
},
)
3.2 Android 端代码
// MainActivity.kt
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "...").setMethodCallHandler { ... }
EventChannel(flutterEngine.dartExecutor.binaryMessenger, "com.example.app/events")
.setStreamHandler(object : EventChannel.StreamHandler {
private var eventSink: EventChannel.EventSink? = null
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
eventSink = events
// 监听网络变化(示例)
val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
// 发送事件到 Flutter
eventSink?.success("WIFI")
}
override fun onCancel(arguments: Any?) {
eventSink = null
}
})
四、Pigeon:类型安全的通信方案
Pigeon 通过代码生成实现类型安全的 Flutter 与原生通信,避免手动处理参数编码/解码。
4.1 配置 Pigeon
# pubspec.yaml
dependencies:
pigeon: ^13.0.0
4.2 定义接口(Dart)
// lib/messages.dart
import 'package:pigeon/pigeon.dart';
@HostApi()
abstract class NativeApi {
@async
int getBatteryLevel();
String getPlatformVersion();
}
@FlutterApi()
abstract class FlutterApi {
void onNetworkStatusChanged(String status);
}
4.3 生成代码
flutter pub run pigeon \
--input lib/messages.dart \
--dart_out lib/messages.g.dart \
--objc_header_out ios/Runner/messages.h \
--objc_source_out ios/Runner/messages.m \
--java_out android/app/src/main/java/com/example/app/Messages.java \
--java_package "com.example.app"
4.4 Android 端实现(Pigeon 生成)
// MainActivity.kt
class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
val api = object : Messages.NativeApi {
override fun getBatteryLevel(): Long {
return getBatteryLevel().toLong()
}
override fun getPlatformVersion(): String {
return "Android ${Build.VERSION.RELEASE}"
}
}
Messages.NativeApi.setup(flutterEngine.dartExecutor.binaryMessenger, api)
}
}
4.5 Flutter 端使用(Pigeon 生成)
// 使用生成的代码(类型安全)
Future<void> _getBattery() async {
final api = Messages.NativeApi();
try {
final level = await api.getBatteryLevel();
print('电池电量:$level');
} catch (e) {
print('调用失败:$e');
}
}
Pigeon 的优势:
- 类型安全,编译时检查错误
- 不需要手动处理参数编码/解码
- 自动生成 Android/iOS/Flutter 三端代码
- 支持复杂数据类型(
List、Map、自定义类)
五、数据类型对照表
| Dart 类型 | Android(Kotlin)类型 | iOS(Swift)类型 |
|---|---|---|
null | null | nil |
bool | Boolean | NSNumber(value: Bool) |
int | Int / Long | NSNumber(value: Int64) |
double | Double | NSNumber(value: Double) |
String | String | String |
Uint8List | ByteArray | FlutterStandardTypedData(bytes: Data) |
Int32List | IntArray | FlutterStandardTypedData(int32s: Data) |
List<?> | List<Any?> | Array<Any> |
Map<String, ?> | Map<String, Any?> | Dictionary<String, Any> |
六、常见问题 FAQ
Q:MethodChannel 调用时应用崩溃,如何排查?
A:检查以下几点:
- Channel 名称在 Flutter 和原生端是否完全一致
- 原生端是否正确注册了
MethodCallHandler - 参数类型是否匹配(Dart 的
int在 Android 端用Int,不要用Double) - 是否在主线程调用(Android 端 UI 操作需要在主线程)
Q:如何在原生端主动调用 Flutter 方法?
A:使用 MethodChannel(双向通信)
// Flutter 端注册方法供原生调用
static const _channel = MethodChannel('com.example.app/native');
void initState() {
super.initState();
_channel.setMethodCallHandler((call) async {
if (call.method == 'onNativeEvent') {
final data = call.arguments as String;
print('收到原生事件:$data');
}
});
}
Q:Pigeon 支持自定义类作为参数的吗?
A:支持,需要在 Dart 端定义 @AllFields 注解的类:
import 'package:pigeon/pigeon.dart';
class User {
final String name;
final int age;
User(this.name, this.age);
}
@HostApi()
abstract class NativeApi {
String getUserInfo(User user);
}
七、参考资源
如果本文对你有帮助,欢迎点赞收藏。如有疑问,欢迎在评论区交流。