Flutter 安卓 Platform 与 Dart 端消息通信方式 Channel 源码解析

2021-08-26 10:31:13 浏览数 (1)

背景

本系列前面已经分析了 Flutter 的很多知识,这一篇我们来看下 Flutter 平台通信相关原理。Flutter 官方提供三种 Platform 与 Dart 端消息通信方式,他们分别是 MethodChannel、BasicMessageChannel、EventChannel,本文会继续延续前面系列对他们进行一个深度解析,源码依赖 Flutter 2.2.3 版本,Platform 选取熟悉的 Android 平台实现。

对于 MethodChannel、BasicMessageChannel、EventChannel 三种官方消息通信方式来说,他们都是全双工通信,所以基于他们我们基本可以实现 Platform 与 Dart 的各种通信能力。他们各自适用场景如下:

  • MethodChanel:用于传递方法调用,MethodCallHandler 最终必须在 UI 线程通过result.success(x)方法返回结果,返回前自己可以异步新起线程做任意耗时操作。
  • BasicMessageChannel:用于传递字符串和半结构化的消息。
  • EventChannel:用于数据流的发送。

基础使用技巧

这些通信方式的基础用法我们这里就不再解释了,这里重点说下技巧,在编写 Platform 代码时有两个特别注意的点:

  • 对于 Mac 用户,如果你要通过 Mac 的 Android Studio 打开 Flutter 自动创建的.android 项目,记得吊起访达后通过快捷键Command Shift '.'显示隐藏目录即可。
  • 修改 Platform 端的代码后如果运行没生效则请关闭 app 重新编译,因为热部署对 Platform 无效。

日常工作中我们使用最多的是 MethodChannel,但是他却不是类型安全的,为了解决这个问题官方推荐使用 Pigeon 包作为 MethodChannel 的替代品,它将生成以结构化类型安全方式发送消息的代码,但是他目前还不稳定。

更多关于他们基础使用案例参见官方文档https://flutter.dev/docs/development/platform-integration/platform-channels。

消息收发传递源码分析

下面源码分析我们依旧秉承以使用方式为入口,分 Platform、Engine、Dart 层各自展开。

Platform 端收发实现流程

在进行 Platform 端源码分析前请先记住下面这幅图,如下 Platform 的 Java 侧源码基于此图展开分析。

在这里插入图片描述在这里插入图片描述

我们先分别看下 MethodChannel、BasicMessageChannel、EventChannel 在 Platform 端的构造成员源码:

代码语言:txt复制
public class MethodChannel {
  private final BinaryMessenger messenger;
  private final String name;
  private final MethodCodec codec;
  //......
  private final class IncomingMethodCallHandler implements BinaryMessageHandler {
    private final MethodCallHandler handler;
  }
}

public final class BasicMessageChannel<T> {
  @NonNull private final BinaryMessenger messenger;
  @NonNull private final String name;
  @NonNull private final MessageCodec<T> codec;
  //......
  private final class IncomingMessageHandler implements BinaryMessageHandler {
    private final MessageHandler<T> handler;
  }
}

public final class EventChannel {
  private final BinaryMessenger messenger;
  private final String name;
  private final MethodCodec codec;
  //......
  private final class IncomingStreamRequestHandler implements BinaryMessageHandler {
    private final StreamHandler handler;
  }
}

可以看到,Platform 端无论哪种方式,他们都有三种重要的成员,分别是:

  • name:String 类型,唯一标识符代表 Channel 的名字,因为一个 Flutter 应用中存在多个 Channel,每个 Channel 在创建时必须指定一个独一无二的 name 作为标识,这点我们在前面系列源码分析中已经见过很多框架实现自己的 name 定义了。
  • messager:BinaryMessenger 类型,充当信使邮递员角色,消息的发送与接收工具人。
  • codec:MethodCodec 或MessageCodec<T>类型,充当消息的编解码器。

所以,MethodChannel、BasicMessageChannel、EventChannel 的 Java 端源码其实自身是没有什么的,重点都在 BinaryMessenger,我们就不贴源码了(比较简单),整个 Java 端收发的流程(以 MethodChannel 为例)大致如下:

在这里插入图片描述在这里插入图片描述

上面流程中的 DartMessenger 就是 BinaryMessenger 的实现,也就是 Platform 端与 Dart 端通信的信使,这一层通信使用的消息格式为二进制格式数据(ByteBuffer)。

可以看到,当我们初始化一个 MethodChannel 实例并注册处理消息的回调 Handler 时会生成一个对应的 BinaryMessageHandler 实例,然后这个实例被放进信使的一个 Map 中,key 就是我们 Channel 的 name,当 Dart 端发送消息到 DartMessenger 信使时,信使会根据 name 找到对应 BinaryMessageHandler 调用,BinaryMessageHandler 中通过调用 MethodCodec 解码器进行二进制解码(默认 StandardMethodCodec 解码对应平台数据类型),接着我们就可以使用解码后的回调响应。

当我们通过 Platform 调用 Dart 端方法时,也是先通过 MethodCodec 编码器对平台数据类型进行编码成二进制格式数据(ByteBuffer),然后通过 DartMessenger 信使调用 FlutterJNI 交给 Flutter Engine 调用 Dart 端对应实现。

Dart Framework 端收发实现流程

在进行 Dart 端源码分析前请先记住下面这幅图,如下源码基于此图展开分析。

在这里插入图片描述在这里插入图片描述

是不是 Dart 端的像极了 Platform 端收发实现流程图,同理我们看下 Dart Framework 端对应 Channel 实现类成员:

代码语言:txt复制
class MethodChannel {
  final String name;
  final MethodCodec codec;
  final BinaryMessenger? _binaryMessenger;
  //......
}

class BasicMessageChannel<T> {
  final String name;
  final MessageCodec<T> codec;
  final BinaryMessenger? _binaryMessenger;
  //......
}

class EventChannel {
  final String name;
  final MethodCodec codec;
  final BinaryMessenger? _binaryMessenger;
  //......
}

可以看到,Dart 端无论哪种方式,他们也都有三种重要的成员,分别是 name、codec、_binaryMessenger,而且他们的职责和 Platform 端完全一样。也就是说 Dart 端就是 Platform 端的一个镜像实现而已,框架设计到原理步骤完全一致,区别仅仅是实现语言的不同。

所以,整个 Dart 端收发的流程(以 MethodChannel 为例)大致如下:

在这里插入图片描述在这里插入图片描述

有了上图不用再贴代码了吧,和 Platform 端如出一辙,只是换了个语言实现而已。

Flutter Engine C 收发实现流程

上面 Platform 与 Dart 端的通信都分析完毕了,现在就差中间粘合层的 Engine 调用了,Engine 的分析我们依然依据调用顺序为主线查看。通过上面分析我们可以得到如下信息:

  • Platform 调用 Dart 时 Java 最终调用了 FlutterJNI 的private native void nativeDispatchPlatformMessage(long nativeShellHolderId, String channel, ByteBuffer message, int position, int responseId)方法传递到 Engine,Engine 最终调用了 Dart Framework 中hooks.dartvoid _dispatchPlatformMessage(String name, ByteData? data, int responseId)方法,然后层层传递到我们的 Widget 中的 MethodChannel。
  • Dart 调用 Platform 时 Dart 最终调用了 PlatformDispatcher 的String? _sendPlatformMessage(String name, PlatformMessageResponseCallback? callback, ByteData? data)方法(即native 'PlatformConfiguration_sendPlatformMessage')传递到 Engine,Engine 最终调用了 Platform 端 FlutterJNI 的public void handlePlatformMessage(final String channel, byte[] message, final int replyId)方法,然后层层传递到我们的 MethodChannel 设置的 MethodCallHandler 回调的 onMethodCall 方法中。

因此我们顺着这两端的入口分析源码可以得到如下调用顺序图:

在这里插入图片描述在这里插入图片描述

上图对应的 Engine C 代码调用及类所属文件都已经交代的很详细了,源码就不再贴片段了,相信你顺着这条链也能根懂源码。特别注意上面 Engine 在负责转发消息时的黄色 TaskRunner,其中 PlatformTaskRunner 就是平台层的主线程(安卓 UI 线程),所以 Channel 在安卓端的回调被切换运行在 UI 线程中,Channel 在 Dart 端的回调被切换运行在 Flutter Dart UI 线程(即 UITaskRunner 中)。

消息编解码源码分析

搞懂了 Channel 的收发流程,你可能对上面的编解码器还有疑惑,他是怎么做到 Dart 与不同平台语言类型间转换的?

我们都知道,一般跨语言或平台传输对象首选方案是通过 json 或 xml 格式,而 Flutter 也不例外,譬如他也提供了 JSONMessageCodec、JSONMethodCodec 等编解码器,同样也是将二进制字节流转换为 json 进行处理,像极了我们 http 请求中字节流转字符串转 json 转对象的机制,这样就抹平了平台差异。

对于 Flutter 的默认实现来说,最值得关注的就是 StandardMethodCodec 和 StandardMessageCodec,由于 StandardMethodCodec 是对 StandardMessageCodec 的一个包装,所以本质我们研究下 StandardMessageCodec 即可。如下:

代码语言:txt复制
public class StandardMessageCodec implements MessageCodec<Object> {
  //把Java对象类型Object转为字节流ByteBuffer
  @Override
  public ByteBuffer encodeMessage(Object message) {
    //......
    final ExposedByteArrayOutputStream stream = new ExposedByteArrayOutputStream();
    writeValue(stream, message);
    final ByteBuffer buffer = ByteBuffer.allocateDirect(stream.size());
    buffer.put(stream.buffer(), 0, stream.size());
    return buffer;
  }
  //把字节流ByteBuffer转为Java对象类型Object
  @Override
  public Object decodeMessage(ByteBuffer message) {
    //......
    message.order(ByteOrder.nativeOrder());
    final Object value = readValue(message);
    //......
    return value;
  }
  //......
}

可以看到,在 Platform 端(Android Java)StandardMessageCodec 的作用就是字节流转 Java 对象类型,Java 对象类型转字节流,核心本质是 StandardMessageCodec 的 readValue 和 writeValue 方法,如下:

代码语言:txt复制
protected void writeValue(ByteArrayOutputStream stream, Object value) {
  if (value == null || value.equals(null)) {
    stream.write(NULL);
  } else if (value instanceof Boolean) {
    stream.write(((Boolean) value).booleanValue() ? TRUE : FALSE);
  } else if (value instanceof Number) {
    if (value instanceof Integer || value instanceof Short || value instanceof Byte) {
      stream.write(INT);
      writeInt(stream, ((Number) value).intValue());
    } else if (value instanceof Long) {
      stream.write(LONG);
      writeLong(stream, (long) value);
    } else if (value instanceof Float || value instanceof Double) {
      stream.write(DOUBLE);
      writeAlignment(stream, 8);
      writeDouble(stream, ((Number) value).doubleValue());
    } else if (value instanceof BigInteger) {
      stream.write(BIGINT);
      writeBytes(stream, ((BigInteger) value).toString(16).getBytes(UTF8));
    } else {
      throw new IllegalArgumentException("Unsupported Number type: "   value.getClass());
    }
  } else if (value instanceof String) {
    stream.write(STRING);
    writeBytes(stream, ((String) value).getBytes(UTF8));
  } else if (value instanceof byte[]) {
    stream.write(BYTE_ARRAY);
    writeBytes(stream, (byte[]) value);
  } else if (value instanceof int[]) {
    stream.write(INT_ARRAY);
    final int[] array = (int[]) value;
    writeSize(stream, array.length);
    writeAlignment(stream, 4);
    for (final int n : array) {
      writeInt(stream, n);
    }
  } else if (value instanceof long[]) {
    stream.write(LONG_ARRAY);
    final long[] array = (long[]) value;
    writeSize(stream, array.length);
    writeAlignment(stream, 8);
    for (final long n : array) {
      writeLong(stream, n);
    }
  } else if (value instanceof double[]) {
    stream.write(DOUBLE_ARRAY);
    final double[] array = (double[]) value;
    writeSize(stream, array.length);
    writeAlignment(stream, 8);
    for (final double d : array) {
      writeDouble(stream, d);
    }
  } else if (value instanceof List) {
    stream.write(LIST);
    final List<?> list = (List) value;
    writeSize(stream, list.size());
    for (final Object o : list) {
      writeValue(stream, o);
    }
  } else if (value instanceof Map) {
    stream.write(MAP);
    final Map<?, ?> map = (Map) value;
    writeSize(stream, map.size());
    for (final Entry<?, ?> entry : map.entrySet()) {
      writeValue(stream, entry.getKey());
      writeValue(stream, entry.getValue());
    }
  } else {
    throw new IllegalArgumentException("Unsupported value: "   value);
  }
}

不用解释了吧,这不就是枚举一堆支持的类型然后按照字节位数截取转换的操作,所以这也就是为什么官方文档中明确枚举了 Channel 支持的数据类型,如下:

在这里插入图片描述在这里插入图片描述

上面是 Platform 端对象类型与二进制之间的转换原理,对于 Dart 端我想你应该也就懂了,无非也是类似操作,不再赘述。

总结

上面全程都以 MethodChannel 进行了源码分析,其他 Channel 我们没有进行分析,但其实本质都一样,仅仅是一种封装而已,希望你有需求的时候知道怎么举一反三。

0 人点赞