底层原理 - 深入探索Platform Channel
Flutter 提供了完善的 Platform Channel 机制供 Native 与 Flutter 端进行相互通信,本节由浅入深,详细探索 Platform Channel 的实现细节。
怎么用
首先我们简单介绍下 Platform Channel 的使用姿势,Flutter提供了三种Channel的方式:
- BaseMessageChannel,消息的传递,用于数据的传递。
- MethodChannel,方法的传递,用于方法调用,传递参数并返回执行结果。
- EventChannel,事件的传递,用于Native端对Flutter端高频的通知触发。
MethodChannel - Android
构造函数,通常messenger传递FlutterNativeView实现的DartExecutor, name是Channel匹配的key值,codec是消息传递的编解码,默认是StandardMethodCodec。
1 | MethodChannel(BinaryMessenger messenger, String name, MethodCodec codec) |
调用Flutter端方法,参数依次是方法名、参数以及返回值的回调。
1 | invokeMethod(String method, Object arguments, MethodChannel.Result callback) |
设置方法被调用的处理器,Flutter调用原生代码会调用handler的onMethodCall方法。
1 | setMethodCallHandler(MethodChannel.MethodCallHandler handler) |
MethodChannel - iOS
构造函数
1 | - (instancetype)initWithName:(NSString*)name |
调用Flutter方法
1 | - (void)invokeMethod:(NSString*)method |
设置方法回调
1 | - (void)setMethodCallHandler:(FlutterMethodCallHandler _Nullable)handler; |
MethodChannel - Flutter
构造函数
1 | - MethodChannel(this.name, [this.codec = const StandardMethodCodec()]) |
调用Native方法
1 | - invokeMethod(String method, [dynamic arguments]) |
设置方法回调
1 | - setMethodCallHandler(Future<dynamic> handler(MethodCall call)) |
BaseMessageChannel、EventChannel和MethodChannel的使用方式十分类似,这里就不一一赘述了。
怎么实现的
Platform Channel 以 engine 为媒介,在 native 端或者 dart 端进行消息的编码,经过 engine 传递,在另一端进行消息的解码,完成整个消息的通信。这个过程涉及到 messager (信使)、
codec(编解码器)以及 handler(处理器),下面将分别介绍。
messager
messager 在整个消息传递过程中,作为信使的角色,实现了完整的消息传递。我们看一下 Android 端 BinaryMessenger 的代码,iOS 和 Dart 部分 API 高度一致。
1 | public interface BinaryMessenger { |
BinaryMessenger 提供了两个 send 方法,可选择是否回调消息的处理结果,提供了 setMessageHandler 用以处理 Dart 端 Channel 调用的消息处理。BinaryMessenger 的一个具体实现在 DartMessenger,维护了 messageHandlers 和 pendingReplies 两个 HashMap 分别处理 Dart 端消息请求的响应以及自己发送消息之后的回调。具体的消息传递的完成流程我们等会再讲,先分析一下 codec(编解码器)和 handler(处理器)的实现细节。
codec
让消息在 dart、engine 以及平台( Android / iOS )之间自由传递,需要将消息在传递前编码为平台无关的二进制数据,在接收后解码为原始的数据格式。Flutter 提供了两种 codec , BaseMessageChannel 使用了 MessageCodec , EventChannel 和 MethodChannel 使用了 MethodCodec。
MessageCodec 提供了 encodeMessage 和 decodeMessage 两个接口,以 Android 为例,它的代码实现如下:
1 | public interface MessageCodec<T> { |
Flutter 官方默认已经实现的 MessageCodec 有 StandardMessageCodec、BinaryCodec、StringCodec、JSONMessageCodec 。默认已经实现的 MethodCodec 有 StandardMethodCodec、JSONMethodCodec。
Codec | Desc | |
---|---|---|
MessageCodec | StandardMessageCodec | 支持基础数据格式与二进制之间的转换,包括 Boolean 、Number、String、Byte、List、Map、数组等。 |
BinaryCodec | 支持二进制数据的直接传递,实际上是没有做任何编解码,可以通过它做大内存块的数据传递。 | |
StringCodec | 支持字符串(UTF-8)与二进制之间的转换。 | |
JSONMessageCodec | 支持把数据转换为JSON,再调用 StringCodec 的编解码。 | |
MethodCodec | StandardMethodCodec | 调用 StandardMessageCodec 传递方法的名字和参数。 |
JSONMethodCodec | 调用 JSONMessageCodec 传递方法的名字和参数。 |
Handler
Flutter 提供了 Handler 作为消息的处理器,用来处理接收方获取的消息。针对 BaseMessageChannel、MethodChannel 和 EventChannel 三种 Channel 分别提供了 IncomingMessageHandler、IncomingMethodCallHandler 和 IncomingStreamRequestHandler 三种 Handler,实际上是分别包装了 MessageHandler 、MethodCallHandler 和 StreamHandler 来处理对方发送过来的消息。
MessageHandler
1 | public interface MessageHandler<T> { |
onMessage 接收获取的 message ,并提供回调 reply 。
MethodCallHandler
1 | public interface MethodCallHandler { |
onMethodCall 响应调用的函数,并提供函数的返回值 Result。
StreamHandler
1 | public interface StreamHandler { |
onListen 开启监听并发送数据,onCancel 取消监听。
消息传递过程
分析 Platform Channel 一个消息的完整调用路径:
native -> dart
从 DartMessenger 的 send 方法开始,
Java 通过 JNI 、iOS 直接调用 C/C++代码,
把消息通过 dispatchPlatformMessage 方法传递到 engine 层的 PlatformView 中,
然后分别经过 engine 、RuntimeController 最后调用 window 的 dispatchPlatformMessage 方法。
window 会自增一个 response_id 并创建一个 response 保存在 pending_responses_ 队列中,通过 tonic 库中的 DartInvokeField 方法调用 Dart 端的 _dispatchPlatformMessage,
完成了一个消息从 native 到 dart 的传递,
dart 端调用 window 的 onPlatformMessage 方法,
通过 channel name 匹配出已经注册好的 handler ,
执行后 通过 window 的 respondToPlatformMessage 方法回调到 engine 层的 RespondToPlatformMessage 方法。
engine 根据 response_id 在 pending_responses 取出对应的 response 执行 Complete 方法把结果返回给 Native 端,
Native 端在 DartMessenger 中的 handlePlatformMessageResponse 方法中处理具体返回的结果,完成了一整个的消息传递过程。
dart -> native
完整的过程跟 native 到 dart 的逻辑基本一致,除了一些函数调用上的差别,文字描述比较费力,这边是简单的做了一个图比较直观的看到整个消息的具体流向。
Flutter Support Native
在整个 Platform Channel 的实现细节梳理的过程中,我们不仅学习了一个数据通信的完整架构,同时也看到了大量的多种语言之间的相互调用。消息可以在不同的语言中自由的流转,主要是各种语言都实现了到 C/C++的函数调用的框架,Java 和 OC 已经有很成熟的支持 Native 的方案,Dart 也提供对 C/C++ 代码的调用的技术,具体的用法如下:
Dart 调用 C/C++ 代码
Dart 对 C/C++ 的支持目前还并不完善,社区中吐槽 Dart 对 Native 支持进展缓慢的帖子很多,主要的嘈点是目前官方对于 Native 的支持文章 Native extensions for the standalone Dart VM 还是 12 年 5 月份撰写的, 笔者也是根据这篇文章的指南调通了 Dart 对 C++ 的调用。在C++中实现了一个简单的加法运算,打包成 lib 提供给 Dart 调用。部分主要代码如下,完整工程可以在 Github 上查看,欢迎 Star 。
native_plus.cc
1 | //第一次调用给定名称的本地函数时,将本地函数的 Dart 名称解析为 C 函数指针。 |
C++代码可以根据不同平台 build 对应的链接库,我这边测试了在 MacOS上的导出,具体步骤如下:
使用 Xcode 创建本地扩展 dylib 项目:
1、选择新建类型:File -> New -> Project -> macOS -> Library
2、填写项目选项:
- Project Name:native_plus
- Framework:None(Plain C/C++ Library)
- Type:Dynamic
3、添加 native_plus.cc 文件到项目中。
4、在 Build Settings 中进行以下更改,在对话框中选择 Build 选项卡和 All Configurations :
- 在 Linking 部分, Other Linker Flags 项中,增加 -undefined dynamic_lookup 。
- 在 Search Paths 部分, Header Search Paths 项中,增加 dart_api.h 文件路径,在文件位于已下载的 SDK 或 Dart 仓库中。
- 在 Preprocessing 部分, Preprocessor Macros 项中,增加 DART_SHARED_LIB=1 。
5、Build。
test_native_plus.dart
1 | //导入本地库,将 Build 生成的 libnative_plus.dylib 放置在同目录下 |
另外还可以使用 [C & C++ interop using FFI] (https://dart.dev/server/c-interop) 对已经开发好的 C/C++ 链接库直接调用。
Tonic
Dart 对 Native 的支持目前还不是很完善,Flutter 团队目前开发的 Tonic 库很好的去支持 Dart 与 C/C++ 代码的相互调用。Tonic 的代码仓库地址在这里。
A collection of C++ utilities for working with the DartVM API. 通过 Tonic 可以完整实现 Dart 与 C++ 之间的代码交互。
总结
本文详细梳理了 Platform Channel 完整的实现细节,最大的收获是对数据通信整体架构的设计和 Dart 与 C/C++ 之间代码的相互调用实现。行文比较仓促,作为一个 Android Coder ,部分案例也是基于 Android 的角度分析代码,可能对于 iOS 同学有一些不太友好,还望海涵。文中涉及到的任何内容,欢迎大家留言讨论。