Flutter与Native通信原理

东方盛慧科技大约 5 分钟flutterflutter

Flutter与Native通信原理

PlatformChannel功能简介

  • BasicMessageChannel: 用于传递数据。Flutter与原生项目的资源是不共享的,可以通过BasicMessageChannel来获取Native项目的图标等资源。
  • MethodChannel: 传递方法调用。Flutter主动调用Native的方法,并获取相应的返回值。比如获取系统电量,发起Toast等调用系统API,可以通过这个来完成。
  • EventChannel: 传递事件。这里是Native将事件通知到Flutter。比如Flutter需要监听网络情况,这时候MethodChannel就无法胜任这个需求了。EventChannel可以将Flutter的一个监听交给Native,Native去做网络广播的监听,当收到广播后借助EventChannel调用Flutter注册的监听,完成对Flutter的事件通知。

消息编解码MessageCodec有4个子类:

  • StandardMessageCodec: StandardMessageCodec是BasicMessageChannel的默认编解码器,其支持基础数据类型、二进制数据、列表、字典,其工作原理会在下文中详细介绍。
  • StringCodec: StringCodec用于字符串与二进制数据之间的编解码,其编码格式为UTF-8。
  • JSONMessageCodec: JSONMessageCodec用于基础数据与二进制数据之间的编解码,其支持基础数据类型以及列表、字典。其在iOS端使用了NSJSONSerialization作为序列化的工具,而在Android端则使用了其自定义的JSONUtil与StringCodec作为序列化工具。
  • BinaryCodec: BinaryCodec是最为简单的一种Codec,因为其返回值类型和入参的类型相同,均为二进制格式(Android中为ByteBuffer,iOS中为NSData)。实际上,BinaryCodec在编解码过程中什么都没做,只是原封不动将二进制数据消息返回而已。或许你会因此觉得BinaryCodec没有意义,但是在某些情况下它非常有用,比如使用BinaryCodec可以使传递内存数据块时在编解码阶段免于内存拷贝。

方法编解码MethodCodec有两个子类:

  • StandardMethodCodec: MethodCodec的默认实现,StandardMethodCodec的编解码依赖于StandardMessageCodec,当其编码MethodCall时,会将method和args依次使用StandardMessageCodec编码,写入二进制数据容器。其在编码方法的调用结果时,若调用成功,会先向二进制数据容器写入数值0(代表调用成功),再写入StandardMessageCodec编码后的result。而调用失败,则先向容器写入数据1(代表调用失败),再依次写入StandardMessageCodec编码后的code,message和detail。
  • JSONMethodCodec: JSONMethodCodec的编解码依赖于JSONMessageCodec,当其在编码MethodCall时,会先将MethodCall转化为字典{"method":method,"args":args}。其在编码调用结果时,会将其转化为一个数组,调用成功为[result],调用失败为[code,message,detail]。再使用JSONMessageCodec将字典或数组转化为二进制数据。

通信工具BinaryMessager

  1. BinaryMessenger是Platform端与Flutter端通信的工具,其通信使用的消息格式为二进制格式数据。当我们初始化一个Channel,并向该Channel注册处理消息的Handler时,实际上会生成一个与之对应的BinaryMessageHandler,并以channel name为key,注册到BinaryMessenger中。当Flutter端发送消息到BinaryMessenger时,BinaryMessenger会根据其入参channel找到对应的BinaryMessageHandler,并交由其处理。

  2. Binarymessenger并不知道Channel的存在,它只和BinaryMessageHandler打交道。而Channel和BinaryMessageHandler则是一一对应的。由于Channel从BinaryMessageHandler接收到的消息是二进制格式数据,无法直接使用,故Channel会将该二进制消息通过Codec(消息编解码器)解码为能识别的消息并传递给Handler进行处理。

  3. 当Handler处理完消息之后,会通过回调函数返回result,并将result通过编解码器编码为二进制格式数据,通过BinaryMessenger发送回Flutter端。

具体实现

BasicMessageChannel
  • flutter端
BasicMessageChannel _basicMessageChannel = BasicMessageChannel('my_flutter.io/message', StandardMessageCodec());
 
BasicMessageChannel _basicMessageChannel2 = BasicMessageChannel('my_flutter.io/message2', StandardMessageCodec());
 
Future<void> _sendMessage() async {
  String reply = await _basicMessageChannel.send('发送给Native端的数据');
    debugPrint(reply);
  }
 
void receiveMessage() {
  _basicMessageChannel2.setMessageHandler((message) async {
	debugPrint("message : $message");
	return '返回给Native端';
  });
}

  • native(ios)端代码
let basicChannel = FlutterBasicMessageChannel(name: "my_flutter.io/message", binaryMessenger: flutterViewController.binaryMessenger)
  
basicChannel.setMessageHandler { (message, reply) in
  debugPrint(message ?? "my flutter!")
  reply("返回给Flutter端的数据")
}		
  		
  		
let basicChannel2 = FlutterBasicMessageChannel(name: "my_flutter.io/message2", binaryMessenger: flutterViewController.binaryMessenger)
  
basicChannel2.sendMessage("发送给Flutter端数据") { (reply) in
  debugPrint(reply ?? "")
}

MethodChannel
  • flutter端
static const platform = const MethodChannel('samples.flutter.dev/battery');
Future<void> _getBatteryLevel() async {
  String batteryLevel;
  try {		
    final int result = await platform.invokeMethod('getBatteryLevel');
    batteryLevel = 'Battery level at $result % .';
  } on PlatformException catch (e) {
    batteryLevel = "Failed to get battery level: '${e.message}'.";
  }
 
  setState(() {
    debugPrint("batteryLevel : $batteryLevel");
    _batteryLevel = batteryLevel;
  });
}

  • native端
let batteryChannel = FlutterMethodChannel(name: "samples.flutter.dev/battery",
  										binaryMessenger: flutterViewController.binaryMessenger)
batteryChannel.setMethodCallHandler({
  (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
    // Note: this method is invoked on the UI thread.
    guard call.method == "getBatteryLevel" else {
    result(FlutterMethodNotImplemented)
    return
  }
self.receiveBatteryLevel(result: result)
})
 
 
private func receiveBatteryLevel(result: FlutterResult) {
  let device = UIDevice.current
  device.isBatteryMonitoringEnabled = true
  if device.batteryState == UIDevice.BatteryState.unknown {
  result(FlutterError(code: "UNAVAILABLE",
  				message: "Battery info unavailable",
  				details: nil))
  } else {
    result(Int(device.batteryLevel * 100))
  }
}

EventChannel
  • flutter端
//声明eventChannel实例
static const _eventChannel = EventChannel('flutter.io/event');
 
//定义两个回调方法
void _onData(Object data) {
  debugPrint("data: $data");
  if (data is String) {
  setState(() {
    _orientation = data;
  });
 }
}
 
void _onError(Object error) {
  debugPrint("error : $error");
  PlatformException exception = error;
  setState(() {
    _orientation = exception?.message ?? 'unknown.';
  });
}
 
//设置监听
_eventChannel.receiveBroadcastStream().listen(_onData, onError: _onError);

  • native
let eventChannel = FlutterEventChannel(name: "flutter.io/event", binaryMessenger: flutterViewController.binaryMessenger)
eventChannel.setStreamHandler(self)
 
//定义一个全局的回调
var sink: FlutterEventSink? = nil
 
//这是两个flutter引擎的回调方法
//flutter开始监听这个channel时的回调, arguments是flutter传给native的参数,event作为native给flutter回调使用。
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
  sink = events
  // arguments flutter给native的参数
  events("portrait")
 
  NotificationCenter.default.addObserver(self, selector: #selector(self.onBatteryStateDidChange(_:)), name:UIDevice.orientationDidChangeNotification, object: nil)
 
  return nil;
}	
  
//flutter不再监听回调
func onCancel(withArguments arguments: Any?) -> FlutterError? {
  sink = nil
  return nil
}		
 
@objc func onBatteryStateDidChange(_ notification: NotificationCenter) {
  let orientation = UIDevice.current.orientation
 
  switch orientation {
  case .portrait:
  	sink?("portrait")
  case .portraitUpsideDown:
  	sink?("portraitUpsideDown")
  case .landscapeLeft:
  	sink?("landscapeLeft")
  case .landscapeRight:
  	sink?("landscapeRight")
  case .faceUp:
  	sink?("faceUp")
  case .faceDown:
  	sink?("faceDown")
  default:
  	sink?("unknown")
  }
}
上次编辑于:
贡献者: 雷勋