关于flutter:Flutter-安卓-Platform-与-Dart-端消息通信方式-Channel-源码解析

44次阅读

共计 8068 个字符,预计需要花费 21 分钟才能阅读完成。

Flutter 系列文章连载~

  • 《Flutter Android 工程构造及应用层编译源码深入分析》
  • 《Flutter 命令实质之 Flutter tools 机制源码深入分析》
  • 《Flutter 的 runApp 与三棵树诞生流程源码剖析》
  • 《Flutter Android 端 Activity/Fragment 流程源码剖析》
  • 《Flutter Android 端 FlutterInjector 及依赖流程源码剖析》
  • 《Flutter Android 端 FlutterEngine Java 相干流程源码剖析》
  • 《Flutter Android 端 FlutterView 相干流程源码剖析》
  • 《Flutter 绘制动机 VSYNC 流程源码全方位剖析》
  • 《Flutter 安卓 Platform 与 Dart 端音讯通信形式 Channel 源码解析》

背景

本系列后面曾经剖析了 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 端的结构成员源码:

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 实现类成员:

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 即可。如下:

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 办法,如下:

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