阅读 1529

Flutter跨端通信:channel分析(一)

前言

在使用Flutter开发的过程中,不可避免的会需要使用到原生Platform(Android/iOS)的一些能力,这个时候就需要有一种方式能够在Flutter和Platform之间进行通信。

而Platform Channel即是Flutter官方提供的一种方式,下面会围绕这种方式进行一个大致的分析,由于篇幅本篇仅写“简介概括”和“使用介绍”两部分,分析一些类结构时优先采用Dart端的代码,但其实Platform(Android/iOS)端对应的这些类的结构也是一样的原理,跨端通信时,Flutter与Platform的数据和消息传递是一个双向的过程,两边做的事情基本是一样的。

导图

简介

借官方图,Platform Channel是Flutter官方提供的一种跨端通信的方案。 flutter.dev/docs/develo…

Channel类型

Flutter提供了以下三种Channel的类型,它们有一个共同点,都是消息在发送之前被编码为二进制,接收到二进制消息后进行解码。

BasicMessageChannel

用于使用异步消息传递与平台插件进行通信的通道。

注意这里“传递”两个字很关键,与方法调用不一样,这里的核心是支持字符串和半结构化的数据传递。

BasicMessageChannel使用MessageCodec消息编/解码器(一共有4种实现类,对应下文的)进行Flutter与平台插件之间的通信。

此处以Dart端代码简单分析一下BasicMessageChannel结构,Platform端对应类的结构基本一致;

class BasicMessageChannel<T> {

  //1. name用于标识这个channel
  //   codec为消息编解码器,对应MessageCodec的实现类,一共有4个实现类,下文会讲到
  //   binaryMessenger信使,null的时候默认使用单例[ServicesBinding.defaultBinaryMessenger],
  //   对应实现为_DefaultBinaryMessenger,下文会分析到
  const BasicMessageChannel(this.name, this.codec, { BinaryMessenger? binaryMessenger })
  
  省略.......
  
  //2. 将指定的内容通过binaryMessenger发送到此channel上的平台插件。对内容进行encode,对返回结果进行decode。
  Future<T> send(T message) async {
    return codec.decodeMessage(await binaryMessenger.send(name, codec.encodeMessage(message)));
  }
  
  //3. 设置一个消息处理器handler,以从该channel上的平台插件接收消息。
  //   接收到消息后进行decode,然后对处理的结果进行encode返回。
  void setMessageHandler(Future<T> Function(T message)? handler) {
    if (handler == null) {
      binaryMessenger.setMessageHandler(name, null);
    } else {
        // 4.调用 binaryMessenger 设置 handler,下文会对binaryMessenger进行说明
      binaryMessenger.setMessageHandler(name, (ByteData? message) async {
        return codec.encodeMessage(await handler(codec.decodeMessage(message)));
      });
    }
  }
  省略.......
}
复制代码

MethodChannel

用于使用异步方法调用与平台插件进行通信的通道。

与BasicMessageChannel不一样的点在于MethodChannel是支持方法调用来进行通信,不只是单纯的数据传递,这种方式也是开发过程中使用的非常多的一种方式。

MethodChannel使用MethodCodec进行Flutter与平台插件之间的通信。下文会对MethodCodec介绍。

class MethodChannel {
  //   1.binaryMessenger信使,null的时候默认使用单例[ServicesBinding.defaultBinaryMessenger],
  //   对应实现为_DefaultBinaryMessenger,下文会分析到
  const MethodChannel(this.name, [this.codec = const StandardMethodCodec(), BinaryMessenger? binaryMessenger ])
  ....
  // 2.MethodCodec 方法编/解码器,下文会对MethodCodec进行说明
  final MethodCodec codec;
  
  ....
  
  @optionalTypeArgs
  Future<T?> _invokeMethod<T>(String method, { required bool missingOk, dynamic arguments }) async {
    assert(method != null);
    // 3.通过binaryMessenger来发送数据
    final ByteData? result = await binaryMessenger.send(
      name,
      // 4.通过MethodCodec对MethodCall进行编码
      codec.encodeMethodCall(MethodCall(method, arguments)),
    );
    .....
    return codec.decodeEnvelope(result) as T;
  }
  
  //5. 设置一个消息处理器handler,以从该channel上的平台插件接收消息。
  void setMethodCallHandler(Future<dynamic> Function(MethodCall call)? handler) {
    _methodChannelHandlers[this] = handler;
    binaryMessenger.setMessageHandler(
      name,
      handler == null
        ? null
        : (ByteData? message) => _handleAsMethodCall(message, handler),
    );
  }
}
复制代码

EventChannel

用于使用事件流与平台插件进行通信的通道。常用于监听某类事件或者状态时。

EventChannel使用MethodCodec进行Flutter与平台插件之间的通信。下文会对MethodCodec介绍。

EventChannel内部会使用MethodChannel进行方法调用,调用方法后会刷新到Stream,所以通过这个Stream可以达到监听的效果,这里就不放代码了。

Channel小结

看完三种Channel的说明和大致结构,会发现其中涉及BinaryMessenger、MethodCodec以及MessageCodec这些东西,可能感觉会..... 没关系,大概率是因为不知道这一堆的XXXCodec是什么作用而没有一个整体的认识,下面会对这些类型进行一个说明,可以在看完这些类型的说明后再回头理一下这块,形成一个整体上的关系认识。

信使 BinaryMessenger

Flutter跨Platform端发送二进制数据的Messenger。

代表消息信使,是消息的发送与接收的工具,其消息数据为二进制格式。上面提到的三种Channel方式,虽然各自的用途不一样,但是它们跨端通信的工具是一样的,都是这个BinaryMessager。结构如下:

abstract class BinaryMessenger {
  ......
  // 通过channel找到对应注册的处理程序Handler,Handler处理完后通过callback返回结果。
  Future<void> handlePlatformMessage(String channel, ByteData? data, ui.PlatformMessageResponseCallback? callback);
  
  // 给对应channel发送消息
  Future<ByteData?> send(String channel, ByteData? message);

  // 当初始化一个Channel并设置处理消息的Handler时(Channel的setMessageHandler函数调用会调用BinaryMessenger的setMessageHandler函数)
  // 对应的Handler会以channel name作为key,被注册到BinaryMessenger中,默认的实现是以Map存储。
  // 当有一端向另一端发送消息到BinaryMessenger后,BinaryMessenger会根据传入的channel name做为key,从Map中找到对应的Handler进行处理。这个Handler对应下面方法setMessageHandler中的说明的。
  void setMessageHandler(String channel, MessageHandler? handler);
  
  ......
}

复制代码

方法编/解码器 MethodCodec

用于方法调用和封装结果的编/解码器。在encode时会有一个MethodCall类型的对象参数,在这里也需要先提一下MethodCall是在方法调用时使用的一个包装结构,其结构很简单,name = 对应方法名称,arguments = 方法参数,代码简要结构如下:

class MethodCall {

  const MethodCall(this.method, [this.arguments])

  /// 调用的方法名称
  final String method;

  /// 方法对应的参数 
  /// Must be a valid value for the [MethodCodec] used.  
  /// 必须是能给MethodCodec有效使用的值,下文会提供有哪些值是有效的
  final dynamic arguments;

}
复制代码

上文有提到MethodChannel和EventChannel都使用MethodCodec进行Flutter与平台插件之间的通信,而这里的MethodCodec有下面对应的两个实现类;MethodCodec在Dart端的代码也比较简单,注意看其中encode和decode的几个方法即可,结构如下:

abstract class MethodCodec {
  /// 将指定的[methodCall]编码为二进制。
  ByteData encodeMethodCall(MethodCall methodCall);

  /// 从二进制解码指定的[methodCall]。
  MethodCall decodeMethodCall(ByteData? methodCall);

  /// 从二进制解码指定的结果[envelope]。
  dynamic decodeEnvelope(ByteData envelope);

  /// 将成功的[result]编码为二进制。
  ByteData encodeSuccessEnvelope(dynamic result);
  
  /// 将异异常信息编码为二进制。
  ByteData encodeErrorEnvelope({ required String code, String? message, dynamic details});
}
复制代码

下面看一看它的两个实现类:

StandardMethodCodec

标准方法编/解码器。

直接看结构,就是实现MethodCodec中的抽象方法,可以看到其重要的点在于messageCodec变量,这个messageCodec是一个消息编/解码器,主要对MethodCall对象和二进制数据进行编/解码操作,类型为StandardMessageCodec,下文中会分析StandardMessageCodec的编/解码操作:

class StandardMethodCodec implements MethodCodec {
  const StandardMethodCodec([this.messageCodec = const StandardMessageCodec()]);
  
  /// 注意:这里使用StandardMessageCodec类型的消息编/解码器进行MethodCall和二进制数据的编/解码操作。
  final StandardMessageCodec messageCodec;
  
  /// 将指定的[methodCall]编码为二进制。
  @override
  ByteData encodeMethodCall(MethodCall call) {
    final WriteBuffer buffer = WriteBuffer();
    // 编码MethodCall时,将method和args依次使用StandardMessageCodec编码,写入二进制数据容器。
    messageCodec.writeValue(buffer, call.method);
    messageCodec.writeValue(buffer, call.arguments);
    return buffer.done();
  }

  /// 从二进制解码指定的[methodCall]。
  @override
  MethodCall decodeMethodCall(ByteData? methodCall) {
    final ReadBuffer buffer = ReadBuffer(methodCall!);
    final dynamic method = messageCodec.readValue(buffer);
    final dynamic arguments = messageCodec.readValue(buffer);
    if (method is String && !buffer.hasRemaining)
      return MethodCall(method, arguments);
    else
      throw const FormatException('Invalid method call');
  }
  
  ......
}
复制代码

JSONMethodCodec

JSON方法编解码器。

与StandardMethodCodec类似,也是实现MethodCodec,但是注意它使用的消息编/解码器是JSONMessageCodec,下文也会简单说明一下JSONMessageCodec的结构:

class JSONMethodCodec implements MethodCodec {

  /// 注意:在JSONMethodCodec中实际是使用了JSONMessageCodec类型的消息编/解码器进行MethodCall和二进制数据的编/解码操作。
  
  @override
  ByteData encodeMethodCall(MethodCall call) {
    return const JSONMessageCodec().encodeMessage(<String, dynamic>{
      'method': call.method,
      'args': call.arguments,
    })!;
  }

  @override
  MethodCall decodeMethodCall(ByteData? methodCall) {
    final dynamic decoded = const JSONMessageCodec().decodeMessage(methodCall);
    ...
    final dynamic method = decoded['method'];
    final dynamic arguments = decoded['args'];
    if (method is String)
      return MethodCall(method, arguments);
    ...
  }

    ......
}
复制代码

MessageCodec消息编/解码器

消息编码/解码机制,一共有4中实现,其结构如下,仅编码和解码两个抽象方法:

abstract class MessageCodec<T> {
  /// 将泛型消息编码成二进制
  ByteData? encodeMessage(T message);

  /// 从二进制解码为对应泛型的消息
  T decodeMessage(ByteData? message);
}
复制代码

BinaryCodec

BinaryCodec,二进制消息编解码器,是很简单的一种Codec,参数类型和返回类型都是二进制,BinaryCodec在编/解码过程中只是原封不动将二进制数据返回,在传递内存数据时在编/解码阶段可免去内存拷贝,利用BinaryCodec的这个特点在一些特定场景下是很有用的。

class BinaryCodec implements MessageCodec<ByteData?> {

  const BinaryCodec();

  @override
  ByteData? decodeMessage(ByteData? message) => message;

  @override
  ByteData? encodeMessage(ByteData? message) => message;
}
复制代码

StringCodec

StringCodec,字符串消息编解码器,用于字符串与二进制数据之间的编/解码,编码格式为UTF-8。

class StringCodec implements MessageCodec<String?> {
  ...
  @override
  String? decodeMessage(ByteData? message) {
    if (message == null)
      return null;
    return utf8.decoder.convert(message.buffer.asUint8List(message.offsetInBytes, message.lengthInBytes));
  }

  @override
  ByteData? encodeMessage(String? message) {
    if (message == null)
      return null;
    final Uint8List encoded = utf8.encoder.convert(message);
    return encoded.buffer.asByteData();
  }
}
复制代码

JSONMessageCodec

JSONMessageCodec,json消息编解码器,用于基本数据类型与二进制数据之间的编/解码,支持基本数据类型、List、Map,而它实际是使用了StringCodec对于json进行了编/解码操作。

class JSONMessageCodec implements MessageCodec<dynamic> {
  ...
  @override
  ByteData? encodeMessage(dynamic message) {
    if (message == null)
      return null;
      // 实际利用了StringCodec对json字符串进行了编解码操作。
    return const StringCodec().encodeMessage(json.encode(message));
  }

  @override
  dynamic decodeMessage(ByteData? message) {
    if (message == null)
      return message;
    return json.decode(const StringCodec().decodeMessage(message)!);
  }
}
复制代码

StandardMessageCodec

StandardMessageCodec,标准消息编解码器,是BasicMessageChannel的默认编解码器,其支持数据类型如下:

其结构如下:

class StandardMessageCodec implements MessageCodec<dynamic> {
  ....
  /// 以下对应不同的类型type,在写入value之前会根据不同的类型写入对应的type,然后再写入value
  static const int _valueNull = 0;
  static const int _valueTrue = 1;
  static const int _valueFalse = 2;
  static const int _valueInt32 = 3;
  static const int _valueInt64 = 4;
  static const int _valueLargeInt = 5;
  static const int _valueFloat64 = 6;
  static const int _valueString = 7;
  static const int _valueUint8List = 8;
  static const int _valueInt32List = 9;
  static const int _valueInt64List = 10;
  static const int _valueFloat64List = 11;
  static const int _valueList = 12;
  static const int _valueMap = 13;

  // 将消息数据编码成二进制
  @override
  ByteData? encodeMessage(dynamic message) {
    if (message == null)
      return null;
    final WriteBuffer buffer = WriteBuffer();
    // 调用writeValue把消息写入二进制buffer
    writeValue(buffer, message);
    return buffer.done();
  }
  
  // 将二进制数据解码
  @override
  dynamic decodeMessage(ByteData? message) {
    if (message == null)
      return null;
    final ReadBuffer buffer = ReadBuffer(message);
    final dynamic result = readValue(buffer);
    if (buffer.hasRemaining)
      throw const FormatException('Message corrupted');
    return result;
  }

  /// 在写入数据时,会先写一个1个字节长度的值用于表示其type(putUint8),然后再写入其value,这样在取的时候可以通过先取1个字节长度判断是哪种type,再取出值来做相应的转换
  void writeValue(WriteBuffer buffer, dynamic value) {
    if (value == null) {
      buffer.putUint8(_valueNull);
    } else if (value is bool) {
      buffer.putUint8(value ? _valueTrue : _valueFalse);
    } else if (value is double) {  
      buffer.putUint8(_valueFloat64);
      buffer.putFloat64(value);
    } 
    省略...............
  }

  dynamic readValue(ReadBuffer buffer) {
    if (!buffer.hasRemaining)
      throw const FormatException('Message corrupted');
      // 在读取数据的时候先取1个字节出来,这样就能知道是对应哪一种type,然后根据不同type获取value
    final int type = buffer.getUint8();
    return readValueOfType(type, buffer);
  }

  // 通过不同的type,从buffer中读出相应的value
  dynamic readValueOfType(int type, ReadBuffer buffer) {
    switch (type) {
      case _valueNull:
        return null;
      case _valueTrue:
        return true;
      case _valueFalse:
        return false;
      case _valueInt32:
        return buffer.getInt32();
      case _valueInt64:
        return buffer.getInt64();
      省略........
    }
  }
  
  省略........
}
复制代码

关系小结

上面介绍了一下不同“角色”的作用和结构,看到这里的话,就需要在脑海里面形成一个比较完整的关系认识了,可能文字描述对于它们之前的关联性不足以表达完整,并且在看完后可能还需要回头再看一些地方才能理清楚整体的关联性,所以下面借导图的形式整理一下以上各个角色之间的联系(改天要是能找个好画图工具重新画一个关系更清晰的图的话再补上来 -_-!!),希望能形成一个整体的认识: 如果到这里还是感觉..... 那实在抱歉,应该是我没讲清楚就对了....

使用介绍

BasicMessageChannel简单使用

Android端 Kotlin代码,设置Channel与set处理Handler。

  //1. Platform端设置对应BasicMessageChannel
  BasicMessageChannel<String>(flutterEngine.dartExecutor.binaryMessenger, "channel/basic_test", StringCodec.INSTANCE)
                .setMessageHandler { s, reply ->
                    reply.reply("$s, hello word.")
                }
复制代码

Flutter端 Dart代码,设置对应Channel,并调用其send方法传递数据。

  
  /// 2.flutter端设置对应BasicMessageChannel
  final basicMessageChannel =
      const BasicMessageChannel("channel/basic_test", StringCodec());

  void _sendMessageToPlatform() async {
    // 3.进行消息传递
    final result = await basicMessageChannel.send("Flutter");
    print(result);
  }
复制代码
结果:
I/flutter (18527): Flutter, hello word.
复制代码

MethodChannel简单使用

Android端设置MethodChannel以及set处理Handler。

        var i = 0
        // 1.Android端生成一个MethodChannel,"channel/method_test"做为channel name,唯一标识。
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "channel/method_test")
                // 2.设置其handler,flutter端发起调用时会触发到此handler回调
                //    public interface MethodCallHandler {
                //        void onMethodCall(MethodCall call, MethodChannel.Result result);
                //    }
                .setMethodCallHandler { call, result ->
                    // 此处回调在Android主线程中,对应Flutter定义的Platfrom Runner Thread
                    // 调用方法为"incrementCount"时
                    if ("incrementCount" == call.method) {
                        // 3.调用success函数返回结果
                        result.success(++i)
                        // 4.异常情况调用 error
                        // result.error("INVALID", "不合法调用", null)
                    } else {
                        // 5.未实现方法调用 notImplemented
                        result.notImplemented()
                    }
                }

复制代码

Flutter端设置MethodChannel以及调用对应方法。

  /// 1.flutter端创建channel,name="channel/method_test",作为区分不同的channel的标识符
  final channel = const MethodChannel("channel/method_test");
  
  void _incrementCounter1() async {
    try {
      // 2.flutter端通过channel调用平台方法,注意这里是异步方法
      //   调用platform端的incrementCount方法,返回结果result
      final result = await channel.invokeMethod("incrementCount");
      print(result);
    } on PlatformException catch (e) {
      // 返回异常
      print("${e.code} ${e.message} ${e.details}");
    }
  }
复制代码

本次小结

以上仅对于Channel的一些简介概述和使用介绍,在使用场景的选择时会有一些帮助,后面会介绍其通信的流程,以明白其实现跨端通信的原理,并在不同的场景下能够选择不同的跨端通信方案。