美文网首页Flutter圈子Flutter中文社区Flutter
Flutter之旅:平台通道(Platform Channel)

Flutter之旅:平台通道(Platform Channel)

作者: 风少侠 | 来源:发表于2019-03-18 14:21 被阅读22次

    作为一个UI框架,Flutter提供了三种通道来和原生平台通信。

    • BasicMessageChannel:它提供类似于BinaryMessages的基本消息传递服务,但可自定义消息编解码器,支持发送字符串或半结构化消息。
    • MethodChannel:它使用异步方法调用的方式进行平台通信。
    • EventChannel:它使用事件流的方式进行平台通信。
    image.png

    三种方式的设计是非常相似的,分别维护了两个成员属性:

    • 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

    完整代码地址

    相关文章

      网友评论

        本文标题:Flutter之旅:平台通道(Platform Channel)

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