Flutter之旅 -- 与原生互调

作者: 开心wonderful | 来源:发表于2025-08-14 14:45 被阅读0次

本篇文章主要介绍以下几个内容:

  • 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. 小结

  1. MethodChannel 机制: Flutter 通过 MethodChannel 实现跨平台通信,就像国际邮政系统一样,负责消息的编码、传输和解码,确保 Flutter 和原生平台之间的可靠通信。

  2. 异步通信模型: 采用 FutureStream 的异步模型,避免 UI 线程阻塞,提供流畅的用户体验。支持单次调用和持续监听两种模式。

  3. 标准化数据格式: 使用 StandardMessageCodec 作为数据编码器,支持基础类型、集合类型等常用数据格式,确保跨平台数据一致性。

  4. 双向通信能力: 不仅支持 Flutter 调用原生方法,还支持原生平台主动调用 Flutter 方法。

相关文章

网友评论

    本文标题:Flutter之旅 -- 与原生互调

    本文链接:https://www.haomeiwen.com/subject/cjjdojtx.html