Flutter的渲染引擎Skia(新版为Impeller)能绘制绝大多数UI,但调用原生平台API时仍需通过Platform Channel桥接。无论是读取设备传感器、调用蓝牙模块还是集成第三方原生SDK,Platform Channel都是Flutter移动端开发中绕不开的底层通信机制。理解其消息序列化原理和编解码流程,是开发高质量Flutter插件的基础。
Platform Channel消息机制原理
Platform Channel使用二进制消息传递,Flutter侧与原生侧各自维护一个MethodChannel引用,通过名称匹配建立通信管道。消息编码采用StandardMessageCodec,支持null、bool、int、double、String、Uint8List、List、Map等基本类型的自动序列化:
import 'package:flutter/services.dart';
class NativeDeviceInfo {
static const _channel = MethodChannel('com.yunthe/device_info');
static Future
MethodChannel是异步双向通信:Flutter发起调用后,消息通过Dart VM的FFI桥接到原生层,原生层处理完毕后将结果原路返回。EventChannel用于原生侧持续向Flutter推送数据流。
Android原生侧实现
Android侧通过MethodChannel.MethodCallHandler接收Flutter消息,在Activity中注册:
// MainActivity.kt
class MainActivity: FlutterActivity() {
private val CHANNEL = "com.yunthe/device_info"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
.setMethodCallHandler { call, result ->
when (call.method) {
"getDeviceInfo" -> {
val info = hashMapOf(
"model" to android.os.Build.MODEL,
"brand" to android.os.Build.BRAND,
"androidVersion" to android.os.Build.VERSION.RELEASE,
"sdkInt" to android.os.Build.VERSION.SDK_INT,
)
result.success(info)
}
"requestPermission" -> {
val perm = call.argument("permission")
if (perm != null) {
ActivityCompat.requestPermissions(this, arrayOf(perm), 1001)
result.success(true)
} else {
result.error("INVALID_ARG", "permission is null", null)
}
}
else -> result.notImplemented()
}
}
}
}
EventChannel实现需要EventChannel.StreamHandler:
class BatteryEventListener(
private val context: Context, messenger: BinaryMessenger
) : EventChannel.StreamHandler {
private var eventSink: EventChannel.EventSink? = null
private var receiver: BroadcastReceiver? = null
init {
EventChannel(messenger, "com.yunthe/battery_event").setStreamHandler(this)
}
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
eventSink = events
receiver = object : BroadcastReceiver() {
override fun onReceive(ctx: Context?, intent: Intent?) {
val level = intent?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1
val scale = intent?.getIntExtra(BatteryManager.EXTRA_SCALE, -1) ?: -1
if (scale > 0) {
eventSink?.success((level.toFloat() / scale * 100).toInt())
}
}
}
context.registerReceiver(receiver, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
}
override fun onCancel(arguments: Any?) {
eventSink = null
receiver?.let { context.unregisterReceiver(it) }
}
}
iOS原生侧实现
// AppDelegate.swift
@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.yunthe/device_info",
binaryMessenger: controller.binaryMessenger
)
channel.setMethodCallHandler { [weak self] (call, result) in
switch call.method {
case "getDeviceInfo":
let info: [String: Any] = [
"model": Device.current.name ?? "Unknown",
"brand": "Apple",
"iosVersion": UIDevice.current.systemVersion,
]
result(info)
case "requestPermission":
guard let args = call.arguments as? [String: Any],
let perm = args["permission"] as? String else {
result(FlutterError(code: "INVALID_ARG", message: nil, details: nil))
return
}
DispatchQueue.main.async { self?.requestPermission(perm, result: result) }
default:
result(FlutterMethodNotImplemented)
}
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
自定义编解码与插件项目结构
StandardMessageCodec不支持自定义类型序列化。传递复杂对象时使用JSONMethodCodec:
final channel = MethodChannel('com.yunthe/custom', const JSONMethodCodec());
class SensorData {
final double x, y, z;
final int timestamp;
SensorData(this.x, this.y, this.z, this.timestamp);
Map toJson() => {'x': x, 'y': y, 'z': z, 'timestamp': timestamp};
factory SensorData.fromJson(Map j) =>
SensorData(j['x'] as double, j['y'] as double, j['z'] as double, j['timestamp'] as int);
}
final raw = await channel.invokeMethod>('getSensorData');
if (raw != null) print(SensorData.fromJson(raw));
实际项目中Platform Channel代码应封装为独立插件,使用federated platform interface模式:
# 创建插件项目
flutter create --template=plugin --platforms=android,ios -a kotlin -i swift yunthe_device
# lib/yunthe_device.dart - 抽象平台接口
abstract class YuntheDevicePlatform extends PlatformInterface {
YuntheDevicePlatform() : super(token: _token);
static final Object _token = Object();
static YuntheDevicePlatform _instance = MethodChannelYuntheDevice();
static YuntheDevicePlatform get instance => _instance;
Future> getDeviceInfo() {
throw UnimplementedError('not implemented');
}
}
# lib/method_channel_yunthe_device.dart - MethodChannel实现
class MethodChannelYuntheDevice extends YuntheDevicePlatform {
final _channel = const MethodChannel('com.yunthe/device_info');
@override
Future> getDeviceInfo() async {
return await _channel.invokeMethod('getDeviceInfo') ?? {};
}
}
Federated模式让web和桌面端各自提供实现,Dart侧代码完全一致。
线程安全与性能优化
Platform Channel消息在Android/iOS侧默认运行在主线程,耗时操作必须切到后台线程:
// Android - 子线程执行IO后切回主线程
MethodChannel(messenger, CHANNEL).setMethodCallHandler { call, result ->
when (call.method) {
"readLargeFile" -> {
Thread {
try {
val data = readLargeFile(call.argument("path")!!)
runOnUiThread { result.success(data) }
} catch (e: Exception) {
runOnUiThread { result.error("IO_ERROR", e.message, null) }
}
}.start()
}
}
}
// iOS - DispatchQueue
channel.setMethodCallHandler { (call, result) in
DispatchQueue.global(qos: .userInitiated).async {
do {
let data = try self.readLargeFile(call.arguments as! String)
DispatchQueue.main.async { result(data) }
} catch {
DispatchQueue.main.async {
result(FlutterError(code: "IO_ERROR", message: error.localizedDescription, details: nil))
}
}
}
}
高频通信场景(传感器数据流、实时音频),StandardMessageCodec的序列化开销会成为瓶颈。改用BasicMessageChannel + BinaryCodec传递原始二进制:
// Flutter侧
final _binaryChannel = BasicMessageChannel(
'com.yunthe/audio_stream', BinaryCodec(),
);
_binaryChannel.setMessageHandler((ByteData? message) async {
if (message != null) _audioPlayer.write(message);
return null;
});
// Android侧
val binaryChannel = BasicMessageChannel(
messenger, "com.yunthe/audio_stream", BinaryCodec.INSTANCE
)
fun pushAudioData(data: ByteArray) {
val buf = ByteBuffer.allocateDirect(data.size)
buf.put(data); buf.flip()
binaryChannel.send(buf) { _ -> }
}
BasicMessageChannel + BinaryCodec跳过类型编解码,数据直接以ByteBuffer传递。实测传输1MB音频数据耗时从StandardMessageCodec的15ms降至1ms以下,适合实时音视频流和高频传感器场景。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/flutter-yi-dong-duan-shi-zhan-yuan-sheng-tong-xin/