flutter message channel原理
最近在学习flutter插件,对flutter ui层与native层的通信机制进行了了解,记录下对message channel通信机制的理解。
1 为什么要使用message channel?
如果app只涉及到ui渲染以及http请求的话,只使用flutter的ui 框架和http请求框架就可以了,但当需要使用到一些native原生技术支撑,flutter ui框架无法直接调用系统提供的原生接口,这个时候就需要使用flutter的插件来为flutter ui框架与native之间建立通信,message channel就是flutter插件的核心。
2 flutter插件的工作流程
(1)app在启动的时候会由flutter引擎加载flutter插件。
/**
* Generated file. Do not edit.
* This file is generated by the Flutter tool based on the
* plugins that support the Android platform.
*/
@Keep
public final class GeneratedPluginRegistrant {
public static void registerWith(@NonNull FlutterEngine flutterEngine) {
flutterEngine.getPlugins().add(new com.example.flutter_plugin_battery.FlutterPluginBatteryPlugin());
}
}
(2)flutter插件在加载后会回调onAttachedToEngine,并传递FlutterPluginBinding ,通过FlutterPluginBinding 可以获取到flutter引擎提供的能力。在onAttachedToEngine中创建了MethodChannel 和EventChannel ,并设置了setMethodCallHandler和setStreamHandler回调。
/** FlutterPluginBatteryPlugin */
public class FlutterPluginBatteryPlugin implements FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler {
/// The MethodChannel that will the communication between Flutter and native Android
///
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
/// when the Flutter Engine is detached from the Activity
private MethodChannel channel;
private EventChannel eventChannel;
private FlutterPluginBinding flutterPluginBinding;
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
this.flutterPluginBinding = flutterPluginBinding;
channel = new MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "flutter_plugin_battery");
eventChannel = new EventChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor().getBinaryMessenger(),"event_battery_event");
channel.setMethodCallHandler(this);
eventChannel.setStreamHandler(this);
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
if (call.method.equals("getPlatformVersion")) {
result.success("Android " + android.os.Build.VERSION.RELEASE);
}
if (call.method.equals("getBattertLever")){
if(flutterPluginBinding != null){
Context context = flutterPluginBinding.getApplicationContext();
BatteryManager batteryManager = (BatteryManager)context.getSystemService(BATTERY_SERVICE);
int battery = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
result.success(battery);
}
}
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
this.flutterPluginBinding = null;
channel.setMethodCallHandler(null);
eventChannel.setStreamHandler(null);
}
@Override
public void onListen(Object arguments, final EventChannel.EventSink events) {
events.success(1);
}
@Override
public void onCancel(Object arguments) {
}
}
(3)在 flutter ui层提供给ui层调用的接口。可以看到,MethodChannel(‘flutter_plugin_battery’)与EventChannel(“event_battery_event”)中设置的名字与java插件层设置的名字是一致的。当调用platformVersion方法的时候,java插件层的onMethodCall会调用并执行相应的方法。
import 'dart:async';
import 'package:flutter/services.dart';
class FlutterPluginBattery {
static const MethodChannel _channel =
const MethodChannel('flutter_plugin_battery');
static const EventChannel _eventChannel = EventChannel("event_battery_event");
static Future<String> get platformVersion async {
final String version = await _channel.invokeMethod('getPlatformVersion');
return version;
}
static Future<int> get batteryLever async{
int batteryLever = await _channel.invokeMethod('getBattertLever');
return batteryLever;
}
static Stream<dynamic> get onBatteryChanger{
return _eventChannel.receiveBroadcastStream();
}
}
3 上述过程的原理是什么?
(1) methodChannel的原理
当在flutter ui层创建methodChannel的时候,先看下methodChannel的构造函数。name就是传入的mechodChannel的唯一的名字,codec和binaryMessenger是可选参数,分别是方法编码器和通道信使。
const MethodChannel(this.name, [this.codec = const StandardMethodCodec(), BinaryMessenger binaryMessenger ])
: assert(name != null),
assert(codec != null),
_binaryMessenger = binaryMessenger;
/// The logical channel on which communication happens, not null.
final String name;
/// The message codec used by this channel, not null.
final MethodCodec codec;
接着看methodChannel的_invokeMethod方法,关键看binaryMessenger.send(
name,
codec.encodeMethodCall(MethodCall(method, arguments)),
);
,MethodCall封装了调用方法与参数,通过codec.encodeMethodCall方法将调用方法与参数转化为二进制流,最后通过binaryMessenger.send方法将通道name与二进制流发送给flutter引擎。
Future<T> _invokeMethod<T>(String method, { bool missingOk, dynamic arguments }) async {
assert(method != null);
final ByteData result = await binaryMessenger.send(
name,
codec.encodeMethodCall(MethodCall(method, arguments)),
);
if (result == null) {
if (missingOk) {
return null;
}
throw MissingPluginException('No implementation found for method $method on channel $name');
}
return codec.decodeEnvelope(result) as T;
}
flutter引擎收到消息后会查找与name对用的BinaryMessageHandler,BinaryMessageHandler是由java插件层设置回调设置的。
public void setMethodCallHandler(final @Nullable MethodCallHandler handler) {
messenger.setMessageHandler(
name, handler == null ? null : new IncomingMethodCallHandler(handler));
}
找到后,会通过jni反向调用java插件层的回调对象并调用
onMessage(ByteBuffer message, final BinaryReply reply)方法,其中,message表示封装的二进制方法流,reply是flutter引擎封装的对象,通过该对象可以将结果反馈给ui框架层的方法调用对象。codec.decodeMethodCall(message)会将二进制流解码成MethodCall 对象提供给上层使用。
@Override
@UiThread
public void onMessage(ByteBuffer message, final BinaryReply reply) {
final MethodCall call = codec.decodeMethodCall(message);
try {
handler.onMethodCall(
call,
new Result() {
@Override
public void success(Object result) {
reply.reply(codec.encodeSuccessEnvelope(result));
}
@Override
public void error(String errorCode, String errorMessage, Object errorDetails) {
reply.reply(codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails));
}
@Override
public void notImplemented() {
reply.reply(null);
}
});
}
这样,flutter ui层调用native层的流程就通了。
(2)EventChannel 的原理
在flutter ui 层调用eventChannel的receiveBroadcastStream方法后,首先创建了MethodChannel,设置messageHandler回调,用于接收methodChannel的消息。在这里我纠结了很久,这个messageHandler会不会接受到flutter ui层和插件层发送的channel 消息呢?我猜是不会的,因为这里设置的并不是binaryMessageHandler ,flutter 引擎应该是在BinaryMessager 中寻找对应channel 的 binaryMessageHandler 来处理channel message 的。因此这里应该只是设置了binaryMessager 对应channel 的reply回调,用于接收 flutter 插件层的reply消息。
同时调用invokeMethod(‘listen’, arguments)方法。、
Stream<dynamic> receiveBroadcastStream([ dynamic arguments ]) {
final MethodChannel methodChannel = MethodChannel(name, codec);
StreamController<dynamic> controller;
controller = StreamController<dynamic>.broadcast(onListen: () async {
binaryMessenger.setMessageHandler(name, (ByteData reply) async {
if (reply == null) {
controller.close();
} else {
try {
controller.add(codec.decodeEnvelope(reply));
} on PlatformException catch (e) {
controller.addError(e);
}
}
return null;
});
try {
await methodChannel.invokeMethod<void>('listen', arguments);
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context: ErrorDescription('while activating platform stream on channel $name'),
));
}
}, onCancel: () async {
binaryMessenger.setMessageHandler(name, null);
try {
await methodChannel.invokeMethod<void>('cancel', arguments);
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context: ErrorDescription('while de-activating platform stream on channel $name'),
));
}
});
return controller.stream;
}
在调用Listen方法后会调用java插件层的onListen方法,并传递EventChannel.EventSink events对象。
@Override
public void onMessage(ByteBuffer message, final BinaryReply reply) {
final MethodCall call = codec.decodeMethodCall(message);
if (call.method.equals("listen")) {
onListen(call.arguments, reply);
} else if (call.method.equals("cancel")) {
onCancel(call.arguments, reply);
} else {
reply.reply(null);
}
}
来看下EventChannel.EventSink对象的success和error方法,最终会通过BinaryMessage发送消息给MethodChannel,flutter引擎找到messageHandler 的onMessage方法,发现是调用了 reply.reply(null),将消息发送给了flutter ui框架设置的reply,这样,就监听到了插件层的消息。
@UiThread
public void success(Object event) {
if (hasEnded.get() || activeSink.get() != this) {
return;
}
EventChannel.this.messenger.send(name, codec.encodeSuccessEnvelope(event));
}
@Override
@UiThread
public void error(String errorCode, String errorMessage, Object errorDetails) {
if (hasEnded.get() || activeSink.get() != this) {
return;
}
EventChannel.this.messenger.send(
name, codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails));
}
(3)PlatformView的原理
我这边理解的是flutter 通过platformView 在flutter ui 同一层创建了一块surface用于原生view的渲染,然后通过methodChannel来控制原生组件执行方法。