Flutter之旅:平台通道(Platform Channel)
作为一个UI框架,Flutter提供了三种通道来和原生平台通信。
- BasicMessageChannel:它提供类似于BinaryMessages的基本消息传递服务,但可自定义消息编解码器,支持发送字符串或半结构化消息。
- MethodChannel:它使用异步方法调用的方式进行平台通信。
- EventChannel:它使用事件流的方式进行平台通信。
三种方式的设计是非常相似的,分别维护了两个成员属性:
- name:用来标识通道。
- codec:消息编解码器。
另外,三种方式其实都是通过BinaryMessages来进行消息的传递,它负责将二进制消息发送到平台插件并从平台插件接收二进制消息。最后通过消息编解码器来将二进制信息转换成我们需要的数据类型,注意三种通信方式都是双向的。以BasicMessageChannel为例:
Future<T> send(T message) async {
return codec.decodeMessage(await BinaryMessages.send(name, codec.encodeMessage(message)));
}
细心的同学会发现,图上还有一个OptionalMethodChannel,这并不是一种新的通信方式,而是MethodChannel的进一步封装,如果找不到对应插件,返回的是null,而不再抛出MissingPluginException异常。
class OptionalMethodChannel extends MethodChannel{
...
@override
Future<T> invokeMethod<T>(String method, [dynamic arguments]) async {
try {
final T result = await super.invokeMethod<T>(method, arguments);
return result;
} on MissingPluginException {
return null;
}
}
...
}
BasicMessageChannel
使用介绍
BasicMessageChannel_Flutter.png BasicMessageChannel_Android.png三种通道分别在flutter和其他平台都提供了相关实现,并提供了相似的api,这里以BasicMessageChannel为例展示一下。
成员属性
- name:通道名字,要保证通信的通道在flutter和native端保持一致。
- codec:消息的编解码器,同样要保证通信的通道在flutter和native端保持一致。
- messenger:消息的发送器,类似于flutter中的BinaryMessages。
Api接口
发送消息
flutter端:可以看到发送消息是个异步方法,大概执行顺序就是先将数据编码成字节数据,通过BinaryMessages传输,等待native返回数据,再解码成我们需要的数据,如果native没有返数据,则Future为null。
Future<T> send(T message) async {
return codec.decodeMessage(await BinaryMessages.send(name, codec.encodeMessage(message)));
}
android端:其实实现是类似的,只不过不是使用Future,而是用回调的方式来监听返回数据。
public void send(T message) {
this.send(message, (BasicMessageChannel.Reply)null);
}
public void send(T message, BasicMessageChannel.Reply<T> callback) {
this.messenger.send(this.name, this.codec.encodeMessage(message), callback == null ? null : new BasicMessageChannel.IncomingReplyHandler(callback));
}
接收消息
flutter端:我们可以看到传入的参数是一个异步函数,handler接收的参数为native平台发送的数据,handler的返回值将作为响应返回给native端。
void setMessageHandler(Future<T> handler(T message)) {
if (handler == null) {
BinaryMessages.setMessageHandler(name, null);
} else {
BinaryMessages.setMessageHandler(name, (ByteData message) async {
return codec.encodeMessage(await handler(codec.decodeMessage(message)));
});
}
}
android端:同样是通过接口回调的方式来接收和响应消息的传递。
public void setMessageHandler(BasicMessageChannel.MessageHandler<T> handler) {
this.messenger.setMessageHandler(this.name, handler == null ? null : new BasicMessageChannel.IncomingMessageHandler(handler));
}
public interface MessageHandler<T> {
void onMessage(T message, BasicMessageChannel.Reply<T> reply);
}
示例代码
我们来做这样一个demo,flutter发送一条消息到native,native收到消息后给个回复,并发送一条新的消息到flutter,flutter收到后再回复给native。
flutter:
PluginChannel.listenBasicMessage();
RaisedButton(
onPressed: () {
PluginChannel.sendBasicMessage();
},
child: Text("BasicMessageChannel"),
)
...
class PluginChannel {
static const _basicMessageChannelName = "study_3/basicMessageChannel";
static const _basicMessageChannel = BasicMessageChannel(_basicMessageChannelName, StandardMessageCodec());
static void listenBasicMessage(){
_basicMessageChannel
.setMessageHandler((result) async{
print('flutter listen:$result');
return "flutter response to native";
});
}
static void sendBasicMessage() {
_basicMessageChannel
.send("flutter send to native")
.then((result) {
print('flutter receive response:$result');
});
}
}
android:
private val BASIC_MESSAGE_CHANNEL = "study_3/basicMessageChannel"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(this)
basicMessageChanelDemo()
}
private fun basicMessageChanelDemo(){
BasicMessageChannel(this.flutterView,BASIC_MESSAGE_CHANNEL, StandardMessageCodec.INSTANCE)
.setMessageHandler { any, reply ->
println("android listen:$any")
reply.reply("android response to flutter")
BasicMessageChannel(this.flutterView,BASIC_MESSAGE_CHANNEL, StandardMessageCodec.INSTANCE)
.send("android send to flutter"){
println("android receive response:$it")
}
}
}
日志:
I/System.out(27557): android listen:flutter send to native
I/flutter (27557): flutter receive response:android response to flutter
I/flutter (27557): flutter listen:android send to flutter
I/System.out(27557): android receive response:flutter response to native
MethodChannel
一些想法
MethodChannel通过传递方法名和参数,来达到通信的效果,给我的感觉和BasicMessageChannel并没有本质上的不同,使用BasicMessageChannel传递一个数据,做一系列操作再返回数据,给我的感觉效果是一样的。为了验证自己的想法,大概翻了一下源码:
首先看两者的发送数据方法:
///BasicMessageChannel
Future<T> send(T message) async {
return codec.decodeMessage(await BinaryMessages.send(name, codec.encodeMessage(message)));
}
///MethodChannel
Future<T> invokeMethod<T>(String method, [dynamic arguments]) async {
assert(method != null);
final ByteData result = await BinaryMessages.send(
name,
codec.encodeMethodCall(MethodCall(method, arguments)),
);
if (result == null) {
throw MissingPluginException('No implementation found for method $method on channel $name');
}
final T typedResult = codec.decodeEnvelope(result);
return typedResult;
}
两者内部的实现逻辑基本上是一样的,编码成二进制、BinaryMessages发送、解码。都是调用的BinaryMessages的send方法,唯一区别就是编解码器codec的不同。下面来看看codec的实现:
///BasicMessageChannel StandardMessageCodec
ByteData encodeMessage(dynamic message) {
if (message == null)
return null;
final WriteBuffer buffer = WriteBuffer();
writeValue(buffer, message);
return buffer.done();
}
///MethodChannel StandardMethodCodec
const StandardMethodCodec([this.messageCodec = const StandardMessageCodec()]);
ByteData encodeMethodCall(MethodCall call) {
final WriteBuffer buffer = WriteBuffer();
messageCodec.writeValue(buffer, call.method);
messageCodec.writeValue(buffer, call.arguments);
return buffer.done();
}
我们可以看到StandardMethodCodec中的messageCodec默认为StandardMessageCodec,而且编码方法和StandardMessageCodec的编码方法是一样的。默认实现就是相当于把方法名和参数封装成了一个MethodCall对象,再通过BasicMessageChannel传递。
Api
//发送
Future<T> invokeMethod<T>(String method, [dynamic arguments]) async {}
//接收
void setMethodCallHandler(Future<dynamic> handler(MethodCall call)) {}
Api不再赘述,设计和使用都类似于BasicMessageChannel,发送的时候传入方法名和参数,返回一个Future来监听响应。接收的时候传入一个高阶函数,参数为收到的信息,返回值为要响应的数据。
示例代码
依然是一个flutter和android相互调用的demo。
flutter:
PluginChannel.listenMethod();
onPressed: () {
PluginChannel.sendBasicMessage();
},
class PluginChannel {
static const _methodChannelName = "study_3/methodChannel";
static const _methodChannel = MethodChannel(_methodChannelName);
static void invokeMethod() {
_methodChannel.invokeMethod("getAge", {"name": "lili"}).then((result) {
print('flutter receive response:$result');
});
}
static void listenMethod() {
_methodChannel.setMethodCallHandler((methodCall) async {
print('flutter listen:$methodCall');
return "男";
});
}
}
android:
private val METHOD_CHANNEL = "study_3/methodChannel"
override fun onCreate(savedInstanceState: Bundle?) {
methodChannelDemo()
}
private fun methodChannelDemo(){
MethodChannel(this.flutterView,METHOD_CHANNEL)
.setMethodCallHandler { methodCall, result ->
println("android listen:${methodCall.method} \t ${methodCall.arguments}")
when(methodCall.method){
"getAge" -> {
result.success(getAge(methodCall.argument<String>("name")))
}
}
MethodChannel(this.flutterView,METHOD_CHANNEL)
.invokeMethod("getSex", mapOf(Pair("name","tom")), object : MethodChannel.Result {
override fun notImplemented() {
println("android receive notImplemented")
}
override fun error(p0: String?, p1: String?, p2: Any?) {
println("android receive error")
}
override fun success(p0: Any?) {
println("android receive response:$p0")
}
})
}
}
private fun getAge(name:String?): Int{
return when(name){
"lili" -> 18
"tom" -> 19
"allen" -> 20
else -> 0
}
}
日志:
I/System.out( 9700): android listen:getAge {name=lili}
I/flutter ( 9700): flutter receive response:18
I/flutter ( 9700): flutter listen:MethodCall(getSex, {name: tom})
I/System.out( 9700): android receive response:男
EventChannel
Api
EventChannel并没有分别提供发送和收听消息的方法,它只提供了一个receiveBroadcastStream方法,用于发送消息,同时返回一个流(Stream),用于监听平台插件成功返回的所有事件信息,这个流可以被监听不止一次。因此我们可以用于native端需要持续传递数据到flutter的情况,比如监听电量,调用摄像头等等。
Stream<dynamic> receiveBroadcastStream([dynamic arguments]) {
final MethodChannel methodChannel = MethodChannel(name, codec);
StreamController<dynamic> controller;
controller = StreamController<dynamic>.broadcast(onListen, onCancel);
return controller.stream;
}
上面是receiveBroadcastStream的抽象代码,总共做了两件事:
- 创建一个MethodChannel,传入自己的name和codec属性。
- 创建一个StreamController,并返回流。
StreamController.broadcast是一个命名构造函数,它返回一个广播流,可以不止一次被监听,它是惰性的,在首次被订阅时调用onListen,不再订阅时调用onCancel。如果之后继续订阅,则再次调用onListen。
那么我们来继续看下receiveBroadcastStream中的onListen做了什么:
BinaryMessages.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: 'while activating platform stream on channel $name',
));
}
可以看到onListen中依然是做了两件事情:
- 设置回调来接收平台插件返回的消息。
- 通过之前创建的MethodChannel发送一条消息,方法名定死为listen,参数为receiveBroadcastStream传入的可选参数。
最后再来看下receiveBroadcastStream中的onCancel:
BinaryMessages.setMessageHandler(name, null);
try {
await methodChannel.invokeMethod<void>('cancel', arguments);
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context: 'while de-activating platform stream on channel $name',
));
}
依然两件事:
- 移除这个通道。
- 用之前创建的MethodChannel发送一条消息,方法名定死为cancel,参数为receiveBroadcastStream传入的可选参数。
可以想象,native平台肯定也定义了一个MethodChannel,用来接收listen和cancel方法,我们来验证一下,以Android端为例,EventChannel部分源码:
public void onMessage(ByteBuffer message, BinaryReply reply) {
MethodCall call = EventChannel.this.codec.decodeMethodCall(message);
if (call.method.equals("listen")) {
this.onListen(call.arguments, reply);
} else if (call.method.equals("cancel")) {
this.onCancel(call.arguments, reply);
} else {
reply.reply((ByteBuffer)null);
}
}
我们可以看到,当收到消息的时候,有三种情况:
- 收到listen方法,则调用onListen。
- 收到cancel方法,则调用onCancel。
- 其他,则返回null,此时flutter端收到null则会关闭这个流。
示例代码
flutter:
class PluginChannel {
static const _eventChannelName = "study_3/eventChannel";
static const _eventChannel = EventChannel(_eventChannelName);
static void event() {
_eventChannel.receiveBroadcastStream("event arg")
.listen((result) {
print('flutter listen:$result');
});
}
}
android:
private fun eventChannelDemo(){
EventChannel(this.flutterView,EVENT_CHANNEL)
.setStreamHandler(object : EventChannel.StreamHandler {
override fun onListen(p0: Any?, events: EventChannel.EventSink?) {
println("android onListen:$p0")
events?.success(1)
events?.success(2)
events?.success(3)
events?.success(4)
events?.endOfStream()
events?.success(5)
}
override fun onCancel(p0: Any?) {
println("android onCancel:$p0")
}
})
}
日志:
I/System.out( 9271): android onListen:event arg
I/flutter ( 9271): flutter listen:1
I/flutter ( 9271): flutter listen:2
I/flutter ( 9271): flutter listen:3
I/flutter ( 9271): flutter listen:4
I/System.out( 9271): android onCancel:event arg