本篇文章主要介绍以下几个内容:
- Flutter MethodChannel 的基础使用方法
- MethodChannel 的底层通信原理和消息传递机制
- Flutter 调用原生方法、原生调用 Flutter 方法代码示例
Flutter之旅
1. 最简单的 MethodChannel 使用
Flutter 是通过 MethodChannel 来实现与原生互调方法通信的。
1.1 Flutter 调用原生方法
- Flutter 端基础调用
import 'package:flutter/services.dart';
class SimplePlatformService {
// 1. 创建MethodChannel,指定通道名称
static const MethodChannel _channel = MethodChannel('simple_channel');
// 2. 调用原生方法
static Future<String> getDeviceInfo() async {
try {
final String result = await _channel.invokeMethod('getDeviceInfo');
return result;
} on PlatformException catch (e) {
return "获取失败: ${e.message}";
}
}
// 3. 调用带参数的原生方法
static Future<bool> saveData(String data) async {
try {
final bool result = await _channel.invokeMethod('saveData', {'data': data});
return result;
} on PlatformException catch (e) {
return false;
}
}
}
- Android 端基础实现
// MainActivity.kt
class MainActivity : FlutterActivity() {
private val CHANNEL = "simple_channel"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
.setMethodCallHandler { call, result ->
when (call.method) {
"getDeviceInfo" -> {
val deviceInfo = "${Build.MANUFACTURER} ${Build.MODEL}"
result.success(deviceInfo)
}
"saveData" -> {
val data = call.argument<String>("data")
// 保存数据逻辑
result.success(true)
}
else -> {
result.notImplemented()
}
}
}
}
}
- iOS 端基础实现
// 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: "simple_channel",
binaryMessenger: controller.binaryMessenger)
channel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) in
switch call.method {
case "getDeviceInfo":
let deviceInfo = "\(UIDevice.current.systemName) \(UIDevice.current.systemVersion)"
result(deviceInfo)
case "saveData":
if let args = call.arguments as? Dictionary<String, Any>,
let data = args["data"] as? String {
// 保存数据逻辑
result(true)
} else {
result(false)
}
default:
result(FlutterMethodNotImplemented)
}
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
1.2 原生调用 Flutter 方法
除了 Flutter 调用原生方法,原生平台也可以主动调用 Flutter 方法:
- Flutter 端设置方法处理器
class SimplePlatformService {
static const MethodChannel _channel = MethodChannel('simple_channel');
// 设置原生调用Flutter的方法处理器
static void setupMethodHandler() {
_channel.setMethodCallHandler((call) async {
switch (call.method) {
case "onDataReceived":
String data = call.arguments["data"];
print("收到原生数据: $data");
// 处理数据逻辑
break;
default:
throw PlatformException(code: "UnimplementedMethod", details: "方法未实现");
}
});
}
}
- Android 端调用 Flutter 方法:
// 在需要的地方调用Flutter方法
val arguments = HashMap<String, Any>()
arguments["data"] = "来自Android的数据"
channel.invokeMethod("onDataReceived", arguments)
- iOS 端调用 Flutter 方法:
// 在需要的地方调用Flutter方法
let arguments = ["data": "来自iOS的数据"]
channel.invokeMethod("onDataReceived", arguments: arguments)
上面就是MethodChannel 的基础用法:
- 通道名称:Flutter 和原生端必须使用相同的通道名称;
-
方法调用:Flutter 通过
invokeMethod调用原生方法; - 参数传递:支持基础数据类型和 Map、List 等集合类型;
-
异常处理:使用
try-catch处理PlatformException; - 双向通信:支持 Flutter 调用原生和原生调用 Flutter。
2. MethodChannel 原理解析
一次完整的方法调用背后发生了什么?
当写下这行代码时:
final result = await _methodChannel.invokeMethod('startBleScan');
背后发生了一个复杂而精妙的跨平台通信过程。
想象一下,Flutter 和原生平台就像两个说不同语言的人,而 MethodChannel 就是他们之间的"超级翻译官"。
接下来跟随这位翻译官的工作流程,用源码 (简化自 Flutter 3.24.3版本) 揭开这个神秘面纱。
2.1 第一步:翻译官接收任务
Flutter 说:"翻译官,帮我告诉原生平台开始蓝牙扫描":
// packages/flutter/lib/src/services/platform_channel.dart
class MethodChannel {
Future<T?> invokeMethod<T>(String method, [dynamic arguments]) async {
// 翻译官说:"好的,让我来处理这个请求"
// 1. 翻译官整理信息:将方法名和参数打包
final MethodCall call = MethodCall(method, arguments);
// 2. 翻译官准备"信件":编码为二进制数据
final ByteData? data = codec.encodeMethodCall(call);
// 3. 翻译官发送信件:通过BinaryMessenger传递
final ByteData? reply = await binaryMessenger.send(name, data);
// 4. 翻译官解读回复:解码原生返回的结果
return codec.decodeEnvelope(reply) as T?;
}
}
翻译官的工作流程:
Flutter: "请执行startBleScan"
↓ 翻译官接收任务
MethodChannel: "让我来翻译这个请求"
↓ 翻译官编码信息
BinaryMessenger: "信件已准备好,发送中..."
↓ 翻译官通过专用通道发送
Flutter引擎C++层: "转发给原生平台"
↓
原生平台: "收到请求,开始执行"
2.2 第二步:翻译官的"密码本"
翻译官说:"我需要用标准的'密码本'来编码这个消息,确保原生平台能准确理解":
// packages/flutter/lib/src/services/message_codecs.dart
class StandardMessageCodec {
// 翻译官的"密码本" - 每种数据类型都有专门的编号
static const int _valueNull = 0; // 空值的编号
static const int _valueBool = 1; // 布尔值的编号
static const int _valueString = 5; // 字符串的编号
static const int _valueList = 10; // 列表的编号
static const int _valueMap = 11; // 字典的编号
void writeValue(WriteBuffer buffer, dynamic value) {
if (value == null) {
buffer.putUint8(_valueNull); // 翻译官:这是空值,编号0
} else if (value is String) {
buffer.putUint8(_valueString); // 翻译官:这是字符串,编号5
final bytes = utf8.encode(value);
writeSize(buffer, bytes.length); // 翻译官:字符串长度是...
buffer.putUint8List(bytes); // 翻译官:字符串内容是...
}
// 翻译官:其他类型我也都会处理...
}
}
翻译官的编码过程:
// Flutter说:"startBleScan"
// 翻译官工作:
// 1. "这是一个字符串,我给它编号5"
// 2. "字符串长度是11个字符"
// 3. "内容是s-t-a-r-t-B-l-e-S-c-a-n"
// 4. "最终密码:[5][11][s][t][a][r][t][B][l][e][S][c][a][n]"
// 原生平台收到:[5][11][s][t][a][r][t][B][l][e][S][c][a][n]
// 原生平台解读:"编号5是字符串,长度11,内容是startBleScan"
2.3 第三步:翻译官的"专用邮递系统"
翻译官说:"现在我要通过我的专用邮递系统 BinaryMessenger 来发送这个密码信件":
// packages/flutter/lib/src/services/binding.dart
class _DefaultBinaryMessenger extends BinaryMessenger {
@override
Future<ByteData?> send(String channel, ByteData? message) {
// 翻译官:我要通过Flutter引擎的C++邮递员来发送
return _sendPlatformMessage(channel, message);
}
}
// 这是翻译官与C++邮递员的专线电话
external Future<ByteData?> _sendPlatformMessage(String channel, ByteData? message);
翻译官的邮递系统工作原理:
// 完整的发送和接收过程
class _DefaultBinaryMessenger extends BinaryMessenger {
// 存储等待回复的请求
final Map<int, Completer<ByteData?>> _pendingCalls = {};
int _nextCallId = 0;
@override
Future<ByteData?> send(String channel, ByteData? message) {
// 翻译官:给这个请求分配一个编号
final int callId = _nextCallId++;
final Completer<ByteData?> completer = Completer<ByteData?>();
// 翻译官:记录这个请求,等待回复
_pendingCalls[callId] = completer;
// 翻译官:发送到C++引擎,并告诉它请求编号
_sendPlatformMessage(channel, message, callId);
return completer.future;
}
// 当原生平台回复时,C++引擎会调用这个方法
void _handlePlatformMessage(String channel, ByteData? data, int callId) {
// 翻译官:根据编号找到对应的请求
final completer = _pendingCalls.remove(callId);
if (completer != null) {
// 翻译官:太好了!收到回复,通知Flutter
completer.complete(data);
}
}
}
翻译官的邮递流程:
翻译官的工作台:
┌─────────────────┐
│ Flutter App │ "请帮我发送消息"
│ (Dart代码) │
└─────────┬───────┘
│
▼ 翻译官:好的,我来处理
┌─────────────────┐
│ MethodChannel │ "编码完成,准备发送"
│ (翻译官) │
└─────────┬───────┘
│ binaryMessenger.send()
▼ 翻译官:通过我的专用邮递系统
┌─────────────────┐
│ BinaryMessenger │ "分配编号#123,发送中..."
│ (邮递系统) │
└─────────┬───────┘
│ _sendPlatformMessage(channel, data, 123)
▼ 翻译官:交给C++邮递员
┌─────────────────┐
│ Flutter Engine │ "收到编号#123的信件,转发中"
│ (C++邮递员) │
└─────────┬───────┘
│ JNI/ObjC调用
▼ C++邮递员:送达目的地
┌─────────────────┐
│ Native Code │ "处理完成,回复编号#123"
│ (Android/iOS) │
└─────────┬───────┘
│
▼ 原生平台:发送回复
┌─────────────────┐
│ Flutter Engine │ "收到编号#123的回复"
│ (C++邮递员) │
└─────────┬───────┘
│ _handlePlatformMessage(channel, reply, 123)
▼ C++邮递员:回复送达
┌─────────────────┐
│ BinaryMessenger │ "找到编号#123的请求,通知翻译官"
│ (邮递系统) │
└─────────┬───────┘
│ completer.complete(reply)
▼ 翻译官:任务完成!
┌─────────────────┐
│ Flutter App │ "太好了!收到结果"
│ (Dart代码) │
└─────────────────┘
关键机制:
- 请求编号系统:每个请求都有唯一编号,确保回复能找到对应的请求
- Completer 机制:用于异步等待和通知结果
- 双向通道:既能发送请求,也能接收原生主动发来的消息
2.4 第四步:翻译官的"双向服务"
原生平台说:"翻译官,我发现了新的蓝牙设备,请告诉 Flutter!"
当原生需要主动通知 Flutter 时,翻译官提供反向翻译服务:
// Flutter端:翻译官,请准备接收原生的消息
_methodChannel.setMethodCallHandler((call) async {
if (call.method == 'onBleDeviceFound') {
// Flutter:收到翻译官转达的设备信息
handleDeviceFound(call.arguments);
}
});
翻译官的双向服务源码:
// packages/flutter/lib/src/services/platform_channel.dart
void setMethodCallHandler(Future<dynamic> Function(MethodCall call)? handler) {
if (handler == null) {
binaryMessenger.setMessageHandler(name, null);
} else {
binaryMessenger.setMessageHandler(name, (ByteData? message) async {
// 翻译官:收到原生发来的密码信件,让我解读一下
final MethodCall call = codec.decodeMethodCall(message);
try {
// 翻译官:翻译完成,转告Flutter
final result = await handler(call);
// 翻译官:Flutter处理完了,我把结果编码后回复原生
return codec.encodeSuccessEnvelope(result);
} catch (error) {
// 翻译官:出错了,我告诉原生发生了什么问题
return codec.encodeErrorEnvelope(
code: 'error',
message: error.toString(),
);
}
});
}
}
翻译官的双向对话场景:
实时蓝牙扫描对话:
时间 | 对话内容
-----|----------
T1 | Flutter: "翻译官,告诉原生开始蓝牙扫描"
| 翻译官: "好的,正在翻译发送..."
| 原生: "收到!开始扫描"
| 翻译官: "原生说扫描已开始"
| Flutter: "太好了!"
T2 | 原生: "翻译官,我发现了设备A!"
| 翻译官: "让我翻译一下...Flutter,原生发现了设备A"
| Flutter: "收到!显示在界面上"
| 翻译官: "Flutter说已经显示了"
| 原生: "很好!"
T3 | 原生: "翻译官,又发现设备B了!"
| 翻译官: "Flutter,又有新设备B"
| Flutter: "继续显示"
T4 | Flutter: "翻译官,告诉原生停止扫描"
| 翻译官: "原生,Flutter说停止扫描"
| 原生: "好的,已停止"
翻译官的工作流程对比:
Flutter → 原生 (正向翻译):
Flutter请求 → 翻译官编码 → 邮递系统发送 → 原生执行 → 原生回复 → 翻译官解码 → Flutter收到
原生 → Flutter (反向翻译):
原生通知 → C++引擎接收 → 邮递系统转发 → 翻译官解码 → Flutter处理 → Flutter回复 → 翻译官编码 → 原生收到
2.5 第五步:为什么翻译官要这样工作?
翻译官说:"让我解释一下为什么我要这样设计我的工作流程"
1. 安全隔离 - "我是中立的调解员"
// 翻译官:Flutter和原生就像两个独立的国家
// Flutter运行在Dart VM王国
// 原生运行在Android/iOS王国
// 我作为中立的翻译官,确保双方安全交流,互不干扰
// 好处:一方出问题不会影响另一方
if (flutterCrashes) {
// 原生继续正常运行
nativeContinuesWorking();
}
2. 异步非阻塞 - "我不让任何人等待"
// 翻译官:我的所有服务都是异步的,不让Flutter界面卡住
Future<void> scanDevices() async {
showLoading(); // Flutter:立即显示加载动画
await startBleScan(); // 翻译官:异步处理,Flutter继续响应用户
hideLoading(); // Flutter:收到结果后隐藏加载
}
// 翻译官:用户可以随时取消、滑动界面,不会被我的翻译工作阻塞
3. 统一的数据格式 - "我说标准普通话"
// 翻译官:无论是Android的Kotlin还是iOS的Swift
// 我都用同一套"密码本"(StandardMessageCodec)
// 确保同样的Flutter代码在不同平台上表达的意思完全一致
Map<String, dynamic> deviceInfo = {
"name": "设备A",
"rssi": -45,
"connected": true
};
// 翻译官:这个数据结构在Android和iOS上翻译结果完全相同
// Android收到:Map<String, Object> deviceInfo
// iOS收到:Dictionary<String, Any> deviceInfo
// 含义完全一致!
4. 请求追踪机制 - "我记住每一次对话"
// 翻译官:我给每个请求分配编号,确保回复不会搞混
class TranslatorWorkflow {
final Map<int, String> _conversations = {};
void startConversation(int id, String topic) {
_conversations[id] = topic;
print("翻译官:开始处理#$id号对话,主题:$topic");
}
void finishConversation(int id, String result) {
final topic = _conversations.remove(id);
print("翻译官:完成#$id号对话($topic),结果:$result");
}
}
// 翻译官:即使同时处理100个对话,我也不会搞混
这就是 MethodChannel - 一个由 "专业翻译官" 主导的精密跨平台通信系统!
3. Flutter 调用原生方法示例
以蓝牙扫描为例,展示 Flutter 如何调用原生方法,以及如何处理原生的实时回调。
3.1 Flutter 端实现
// lib/common/channel/platform_service.dart
class PlatformService {
static const MethodChannel _methodChannel = MethodChannel('your_method_channel');
/// 通用调用方法
static Future<ChannelResult> invokeMethod<T>(String methodName, [dynamic args]) async {
try {
final result = await _methodChannel.invokeMethod(methodName, args);
Map<String, dynamic> map = jsonDecode(result);
return ChannelResult.fromMap(map);
} on PlatformException catch (e) {
return ChannelResult(-1, e.message ?? "", false);
}
}
/// 开始蓝牙扫描
static Future<ChannelResult> startBleScan() async {
return invokeMethod("startBleScan");
}
/// 停止蓝牙扫描
static Future<ChannelResult> stopBleScan() async {
return invokeMethod("stopBleScan");
}
// --------------------------- 原生调用Flutter的方法处理 -----------------------------------
/// 蓝牙设备流 - 用于接收原生推送的设备信息
static final StreamController<BluetoothDeviceInfo> _bleDeviceController =
StreamController.broadcast();
static Stream<BluetoothDeviceInfo> get bleDeviceStream =>
_bleDeviceController.stream;
/// 设置原生调用Flutter的方法处理器
static void setupMethodHandler() {
_methodChannel.setMethodCallHandler((call) async {
switch (call.method) {
case "onBleDeviceFound": // 原生发现蓝牙设备时的回调
try {
String? name = call.arguments["name"];
String? rssi = call.arguments["rssi"];
final device = BluetoothDeviceInfo(
name: "$name",
rssi: int.tryParse(rssi ?? "-100") ?? -100,
);
// 将设备信息推送到Stream中
_bleDeviceController.add(device);
} catch (e) {
print("处理蓝牙设备信息失败: $e");
}
break;
default:
throw PlatformException(
code: "UnimplementedMethod",
details: "方法 ${call.method} 未实现");
}
});
}
}
// 蓝牙设备信息模型
class BluetoothDeviceInfo {
final String name;
final int rssi;
BluetoothDeviceInfo({required this.name,required this.rssi});
}
// 统一的响应格式
class ChannelResult {
final int code;
final String message;
final bool success;
final dynamic data;
ChannelResult(this.code, this.message, this.success, [this.data]);
factory ChannelResult.fromMap(Map<String, dynamic> map) {
return ChannelResult(
map['code'] ?? -1,
map['msg'] ?? '',
map['success'] ?? false,
map['data'],
);
}
}
3.2 原生端实现
- Android 端实现
// android/app/src/main/kotlin/.../channel/MethodChannelHandler.kt
class MethodChannelHandler(
private val context: ComponentActivity,
private val channel: MethodChannel
) : MethodChannel.MethodCallHandler {
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"startBleScan" -> {
startBluetoothScan(result)
}
"stopBleScan" -> {
BleUtils.stopBleScan()
result.success(ChannelResult.success(""))
}
else -> {
result.notImplemented()
}
}
}
private fun startBluetoothScan(result: MethodChannel.Result) {
BleUtils.searchBleDevices { devices, error ->
if (error != null) {
result.success(ChannelResult.fail("扫描失败: $error"))
return@searchBleDevices
}
// 扫描成功,开始推送发现的设备
devices?.forEach { device ->
onBleDeviceFound(device)
}
result.success(ChannelResult.success("扫描开始"))
}
}
// 发现蓝牙设备时推送到Flutter
private fun onBleDeviceFound(device: BleDevice) {
val deviceInfo = HashMap<String, Any>()
deviceInfo["name"] = device.deviceName
deviceInfo["rssi"] = device.rssi.toString()
// 调用Flutter方法推送设备信息
MainActivity.invokeMethod("onBleDeviceFound", deviceInfo)
}
}
// MainActivity中的Channel管理
class MainActivity : FlutterFragmentActivity() {
companion object {
const val CHANNEL_NAME = "your_method_channel"
private lateinit var channel: MethodChannel
fun invokeMethod(method: String, arguments: Any?) {
if (::channel.isInitialized) {
channel.invokeMethod(method, arguments)
}
}
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_NAME)
channel.setMethodCallHandler(MethodChannelHandler(this, channel))
}
}
- iOS 端实现
// ios/Runner/AppDelegate.swift
@main
@objc class AppDelegate: FlutterAppDelegate {
let METHOD_NAME = "your_method_channel"
private var controller: FlutterViewController?
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
controller = window?.rootViewController as! FlutterViewController
bindMethodChannel(messenger: controller!.binaryMessenger)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
func bindMethodChannel(messenger: FlutterBinaryMessenger) {
let channel = FlutterMethodChannel(name: METHOD_NAME, binaryMessenger: messenger)
channel.setMethodCallHandler { (call: FlutterMethodCall, flutterResult: @escaping FlutterResult) in
switch call.method {
case "startBleScan":
self.startBleScan(flutterResult: flutterResult)
case "stopBleScan":
self.stopBleScan(flutterResult: flutterResult)
default:
flutterResult(FlutterMethodNotImplemented)
}
}
}
func startBleScan(flutterResult: @escaping FlutterResult) {
BleManager.shared.searchBleDevices(
devicePrefix: "",
transport: .ble,
security: .secure2
) { [weak self] deviceList, error in
guard let self = self else { return }
if let error = error {
self.responseFlutter(result: .fail("扫描失败"), flutterResult: flutterResult)
return
}
// 推送发现的设备到Flutter
if let devices = deviceList {
for device in devices {
self.onBleDeviceFound(device: device)
}
}
self.responseFlutter(result: .success("扫描开始"), flutterResult: flutterResult)
}
}
func stopBleScan(flutterResult: @escaping FlutterResult) {
BleManager.shared.stopBleDevicesSearch()
responseFlutter(result: .success("扫描停止"), flutterResult: flutterResult)
}
// 发现蓝牙设备时通知Flutter
func onBleDeviceFound(device: BleDevice) {
guard let controller = controller else { return }
let channel = FlutterMethodChannel(
name: METHOD_NAME,
binaryMessenger: controller.binaryMessenger
)
let deviceInfo: [String: Any] = [
"name": device.name,
"rssi": "-100"
]
// 实时推送设备信息到Flutter
channel.invokeMethod("onBleDeviceFound", arguments: deviceInfo)
}
}
3.3 业务层使用示例
class BluetoothScanPage extends StatefulWidget {
@override
_BluetoothScanPageState createState() => _BluetoothScanPageState();
}
class _BluetoothScanPageState extends State<BluetoothScanPage> {
final List<BluetoothDeviceInfo> _devices = [];
StreamSubscription<BluetoothDeviceInfo>? _deviceSubscription;
bool _isScanning = false;
@override
void initState() {
super.initState();
// 设置原生调用Flutter的方法处理器
PlatformService.setupMethodHandler();
}
/// 开始扫描蓝牙设备
Future<void> _startScan() async {
setState(() => _isScanning = true);
try {
// 1. 调用原生方法开始扫描
final result = await PlatformService.startBleScan();
if (!result.success) {
throw Exception('扫描失败: ${result.message}');
}
// 2. 监听原生推送的设备信息
_deviceSubscription = PlatformService.bleDeviceStream.listen((device) {
setState(() {
// 去重添加设备
final existingIndex = _devices.indexWhere((d) => d.name == device.name);
if (existingIndex >= 0) {
_devices[existingIndex] = device;
} else {
_devices.add(device);
}
});
});
} catch (e) {
// todo sth
}
}
/// 停止扫描
Future<void> _stopScan() async {
await PlatformService.stopBleScan();
_deviceSubscription?.cancel();
setState(() => _isScanning = false);
}
@override
void dispose() {
_deviceSubscription?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('蓝牙扫描'),
actions: [
IconButton(
icon: Icon(_isScanning ? Icons.stop : Icons.search),
onPressed: _isScanning ? _stopScan : _startScan,
),
],
),
body: ListView.builder(
itemCount: _devices.length,
itemBuilder: (context, index) {
final device = _devices[index];
return ListTile(
leading: Icon(Icons.bluetooth),
title: Text(device.name),
subtitle: Text('信号强度: ${device.rssi} dBm'),
);
},
),
);
}
}
4. 原生调用 Flutter 方法示例
第 3 节中基于前面蓝牙扫描的示例,已经有原生如何调用 Flutter 方法,不再复述。
在上面蓝牙扫描示例中,原生调用 Flutter的 关键实现要点:
- 1. Flutter 端设置方法处理器
// 必须在应用启动时设置方法处理器
static void setupMethodHandler() {
_methodChannel.setMethodCallHandler((call) async {
switch (call.method) {
case "onBleDeviceFound": // 处理原生推送的设备信息
String? name = call.arguments["name"];
String? rssi = call.arguments["rssi"];
final device = BluetoothDeviceInfo(
name: "$name",
rssi: int.tryParse(rssi ?? "-100") ?? -100,
);
// 推送到Stream供UI监听
_bleDeviceController.add(device);
break;
}
});
}
注:在应用启动时就设置 setMethodCallHandler。
- 2. 原生端调用 Flutter 方法
Android 端:
// 发现设备时调用Flutter方法
private fun onBleDeviceFound(device: ESPDevice) {
val deviceInfo = HashMap<String, Any>()
deviceInfo["name"] = device.deviceName
deviceInfo["rssi"] = device.rssi.toString()
// 关键:调用Flutter方法
MainActivity.invokeMethod("onBleDeviceFound", deviceInfo)
}
iOS 端:
// 发现设备时调用Flutter方法
func onBleDeviceFound(device: ESPDevice) {
let deviceInfo: [String: Any] = [
"name": device.name,
"rssi": "-100"
]
// 关键:调用Flutter方法
channel.invokeMethod("onBleDeviceFound", arguments: deviceInfo)
}
- 3. 数据流转机制
原生平台发现设备
↓
调用Flutter方法 (onBleDeviceFound)
↓
Flutter方法处理器接收
↓
推送到Stream
↓
UI监听Stream并更新界面
这种模式适用于大部分需要原生主动推送数据到 Flutter 的场景,如:
- 传感器数据更新
- 网络状态变化
- 系统通知
- 硬件设备状态变化等
5. 注意事项
5.1 数据类型兼容性
MethodChannel 只支持特定的数据类型,需要注意类型转换:
// ❌ 错误:使用不支持的数据类型
class BadExample {
static Future<void> sendComplexData() async {
final data = {
"timestamp": DateTime.now(), // DateTime不被支持
"callback": () => print("hello"), // Function不被支持
"customObject": MyCustomClass(), // 自定义类不被支持
};
await PlatformService.invokeMethod("sendData", data);
}
}
// ✅ 正确:使用支持的数据类型
class GoodExample {
static Future<void> sendComplexData() async {
final data = {
"timestamp": DateTime.now().millisecondsSinceEpoch, // 转换为int
"action": "print_hello", // 使用String标识
"customData": { // 使用Map表示复杂对象
"id": "123",
"name": "example",
"values": [1, 2, 3],
},
};
await PlatformService.invokeMethod("sendData", data);
}
}
支持的数据类型:
- 基础类型:
null,bool,int,double,String - 集合类型:
List,Map - 二进制数据:
Uint8List
5.2 异常处理机制
// 完善的异常处理示例
class PlatformService {
static Future<ChannelResult> safeInvokeMethod(
String methodName, [
dynamic args,
Duration timeout = const Duration(seconds: 30),
]) async {
try {
final result = await _methodChannel
.invokeMethod(methodName, args)
.timeout(timeout);
if (result == null) {
return ChannelResult(-1, "原生方法返回null", false);
}
final Map<String, dynamic> map = jsonDecode(result);
return ChannelResult.fromMap(map);
} on PlatformException catch (e) {
return ChannelResult(-1, e.message ?? "平台异常", false);
} on TimeoutException catch (e) {
return ChannelResult(-1, "调用超时", false);
} on FormatException catch (e) {
return ChannelResult(-1, "数据格式错误", false);
} catch (e) {
return ChannelResult(-1, "未知错误: $e", false);
}
}
}
6. 小结
-
MethodChannel 机制: Flutter 通过
MethodChannel实现跨平台通信,就像国际邮政系统一样,负责消息的编码、传输和解码,确保 Flutter 和原生平台之间的可靠通信。 -
异步通信模型: 采用
Future和Stream的异步模型,避免 UI 线程阻塞,提供流畅的用户体验。支持单次调用和持续监听两种模式。 -
标准化数据格式: 使用
StandardMessageCodec作为数据编码器,支持基础类型、集合类型等常用数据格式,确保跨平台数据一致性。 -
双向通信能力: 不仅支持 Flutter 调用原生方法,还支持原生平台主动调用 Flutter 方法。












网友评论