Platform Channel:Flutter 与原生通信
hardflutterplatform-channelmethod-channelevent-channelbasic-message-channelpigeonffi
为什么需要 Platform Channel?
Flutter 运行在自己的引擎上,无法直接调用平台 API(如蓝牙、相机 native SDK、企业微信 SDK)。Platform Channel 是 Flutter 与原生(Android/iOS)通信的桥梁。
三种 Channel 类型
| Channel | 通信方式 | 适用场景 |
|---|---|---|
MethodChannel |
请求-响应(RPC) | 调用一次性 API(读取设备信息、打开相机) |
EventChannel |
流式(Stream) | 持续数据(传感器、蓝牙、位置更新) |
BasicMessageChannel |
双向消息 | 自定义编解码器、频繁双向通信 |
MethodChannel —— 最常用
Flutter 侧
class BatteryPlugin {
static const _channel = MethodChannel('com.example/battery');
static Future<int> getBatteryLevel() async {
try {
final int level = await _channel.invokeMethod('getBatteryLevel');
return level;
} on PlatformException catch (e) {
throw Exception('Failed to get battery level: ${e.message}');
}
}
// 带参数
static Future<String> convertText(String text, String locale) async {
return await _channel.invokeMethod('convertText', {
'text': text,
'locale': locale,
});
}
}
Android 侧(Kotlin)
// MainActivity.kt
class MainActivity: FlutterActivity() {
private val CHANNEL = "com.example/battery"
override fun configureFlutterEngine(@NonNull 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)
}
"convertText" -> {
val text = call.argument<String>("text") ?: ""
val locale = call.argument<String>("locale") ?: "en"
result.success(text.uppercase(Locale(locale)))
}
else -> result.notImplemented()
}
}
}
private fun getBatteryLevel(): Int {
val batteryManager = getSystemService(BATTERY_SERVICE) as BatteryManager
return batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
}
}
iOS 侧(Swift)
// AppDelegate.swift
@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/battery",
binaryMessenger: controller.binaryMessenger)
channel.setMethodCallHandler { [weak self] call, result in
switch call.method {
case "getBatteryLevel":
self?.receiveBatteryLevel(result: result)
default:
result(FlutterMethodNotImplemented)
}
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
private func receiveBatteryLevel(result: FlutterResult) {
let device = UIDevice.current
device.isBatteryMonitoringEnabled = true
guard device.batteryState != .unknown else {
result(FlutterError(code: "UNAVAILABLE", message: "Battery info unavailable", details: nil))
return
}
result(Int(device.batteryLevel * 100))
}
}
EventChannel —— 持续事件流
// Flutter 侧:订阅传感器数据流
class GyroscopePlugin {
static const _channel = EventChannel('com.example/gyroscope');
static Stream<Map<String, double>> get gyroscopeStream {
return _channel.receiveBroadcastStream().map((event) {
final map = Map<String, double>.from(event as Map);
return map;
});
}
}
// 使用
StreamBuilder<Map<String, double>>(
stream: GyroscopePlugin.gyroscopeStream,
builder: (_, snapshot) {
if (!snapshot.hasData) return const SizedBox();
return Text('x: ${snapshot.data!['x']?.toStringAsFixed(2)}');
},
)
Pigeon —— 类型安全的代码生成
手写 Channel 容易出现字符串 typo、类型不匹配。Pigeon 从 Dart 定义生成类型安全的 Kotlin/Swift 代码:
flutter pub add --dev pigeon
// pigeons/messages.dart(定义 API)
import 'package:pigeon/pigeon.dart';
@ConfigurePigeon(PigeonOptions(
dartOut: 'lib/src/messages.g.dart',
kotlinOut: 'android/app/src/main/kotlin/Messages.g.kt',
swiftOut: 'ios/Runner/Messages.g.swift',
))
class BatteryInfo {
late int level;
late String status; // 'charging' | 'discharging' | 'full'
}
@HostApi()
abstract class BatteryHostApi {
BatteryInfo getBatteryInfo();
@async
BatteryInfo getBatteryInfoAsync();
}
@FlutterApi()
abstract class BatteryFlutterApi {
void onBatteryLow(); // 原生主动通知 Flutter
}
生成后,Flutter 和原生都有强类型调用,彻底告别字符串 typo。
FFI —— 直接调用 C/C++ 库
对于性能敏感的 native 库(如图像处理、算法库),可以用 Dart FFI 直接绑定:
import 'dart:ffi';
import 'dart:io';
final dylib = Platform.isAndroid
? DynamicLibrary.open('libmylib.so')
: DynamicLibrary.process();
typedef NativeAdd = Int32 Function(Int32 a, Int32 b);
typedef DartAdd = int Function(int a, int b);
final add = dylib.lookupFunction<NativeAdd, DartAdd>('add');
print(add(1, 2)); // 3
FFI 不经过 Dart VM 编解码,性能接近原生。
核心考点
Q:Platform Channel 的通信原理?
Channel 通过 BinaryMessenger 传递二进制消息。默认使用 StandardMessageCodec(支持 int/double/String/List/Map),在 Dart 和原生之间序列化/反序列化。通信是异步的,底层通过 PostMessage 机制实现。
Q:三种 Channel 如何选择?
- 一次性调用:
MethodChannel - 连续数据推送:
EventChannel(本质是 StreamController 的单向 Stream) - 需要自定义编解码器或频繁双向消息:
BasicMessageChannel