Flutter 与Native交互
前面我们介绍过如何写一个调用原生 API 的 Flutter 插件,那么到底是怎么实现交互的呢?我这里简单介绍一下。
Flutter 架构概览
Flutter 框架包括:Framework 和 Engine,他们运行在各自的 Embedder 上,通过各个平台的 Platform Channel 与原生进行通信。
Framework 是 Dart 语言开发的,包括 Material (Android) 和 Cupertino(iOS) 风格的 Widgets,以及各种常用的基础 Widgets;还包括渲染、动画、绘制、手势等基础功能。
Engine 是 C++ 实现的,包括 Skia (图形库);Dart VM (Dart运行时);Text(文本渲染)等。
Platform Channel
上面可以看出 Flutter 是通过 Platform Channel 与原生进行通信的,Platform Channel 以 engine 为媒介,在 native 端或者 dart 端进行消息的编码,经过 engine 传递,在另一端进行消息的解码,完成整个消息的通信。这个过程涉及到 messager (信使)、 codec(编解码器)以及 handler(处理器)。
Flutter定义了三种不同类型的Channel,它们分别是
- BasicMessageChannel:用于传递字符串和半结构化的信息。
- MethodChannel:用于传递方法调用(method invocation)。
- EventChannel: 用于数据流(event streams)的通信。
三种Channel之间互相独立,各有用途,但它们在设计上却非常相近。每种Channel均有三个重要成员变量:
- name: String类型,代表Channel的名字,也是其唯一标识符。
- messager:BinaryMessenger类型,代表消息信使,是消息的发送与接收的工具。
- codec: MessageCodec类型或MethodCodec类型,代表消息的编解码器。
class MethodChannel {
const MethodChannel(this.name, [this.codec = const StandardMethodCodec(), BinaryMessenger binaryMessenger ])
: assert(name != null),
assert(codec != null),
_binaryMessenger = binaryMessenger;
final String name;
final MethodCodec codec;
BinaryMessenger get binaryMessenger => _binaryMessenger ?? defaultBinaryMessenger;
final BinaryMessenger _binaryMessenger;
@optionalTypeArgs
Future<T> _invokeMethod<T>(String method, { bool missingOk, dynamic arguments }) async {
assert(method != null);
final ByteData result = await binaryMessenger.send(
name,
codec.encodeMethodCall(MethodCall(method, arguments)),
);
if (result == null) {
if (missingOk) {
return null;
}
throw MissingPluginException('No implementation found for method $method on channel $name');
}
return codec.decodeEnvelope(result) as T;
}
@optionalTypeArgs
Future<T> invokeMethod<T>(String method, [ dynamic arguments ]) {
return _invokeMethod<T>(method, missingOk: false, arguments: arguments);
}
Future<List<T>> invokeListMethod<T>(String method, [ dynamic arguments ]) async {
final List<dynamic> result = await invokeMethod<List<dynamic>>(method, arguments);
return result?.cast<T>();
}
Future<Map<K, V>> invokeMapMethod<K, V>(String method, [ dynamic arguments ]) async {
final Map<dynamic, dynamic> result = await invokeMethod<Map<dynamic, dynamic>>(method, arguments);
return result?.cast<K, V>();
}
void setMethodCallHandler(Future<dynamic> handler(MethodCall call)) {
_methodChannelHandlers[this] = handler;
binaryMessenger.setMessageHandler(
name,
handler == null
? null
: (ByteData message) => _handleAsMethodCall(message, handler),
);
}
bool checkMethodCallHandler(Future<dynamic> handler(MethodCall call)) => _methodChannelHandlers[this] == handler;
void setMockMethodCallHandler(Future<dynamic> handler(MethodCall call)) {
_methodChannelMockHandlers[this] = handler;
binaryMessenger.setMockMessageHandler(
name,
handler == null ? null : (ByteData message) => _handleAsMethodCall(message, handler),
);
}
bool checkMockMethodCallHandler(Future<dynamic> handler(MethodCall call)) => _methodChannelMockHandlers[this] == handler;
Future<ByteData> _handleAsMethodCall(ByteData message, Future<dynamic> handler(MethodCall call)) async {
final MethodCall call = codec.decodeMethodCall(message);
try {
return codec.encodeSuccessEnvelope(await handler(call));
} on PlatformException catch (e) {
return codec.encodeErrorEnvelope(
code: e.code,
message: e.message,
details: e.details,
);
} on MissingPluginException {
return null;
} catch (e) {
return codec.encodeErrorEnvelope(code: 'error', message: e.toString(), details: null);
}
}
}
安卓中的 MethodChannel
public final class MethodChannel {
private static final String TAG = "MethodChannel#";
private final BinaryMessenger messenger;
private final String name;
private final MethodCodec codec;
public MethodChannel(BinaryMessenger messenger, String name) {
this(messenger, name, StandardMethodCodec.INSTANCE);
}
public MethodChannel(BinaryMessenger messenger, String name, MethodCodec codec) {
this.messenger = messenger;
this.name = name;
this.codec = codec;
}
@UiThread
public void invokeMethod(@NonNull String method, @Nullable Object arguments) {
this.invokeMethod(method, arguments, (MethodChannel.Result)null);
}
@UiThread
public void invokeMethod(String method, @Nullable Object arguments, MethodChannel.Result callback) {
this.messenger.send(this.name, this.codec.encodeMethodCall(new MethodCall(method, arguments)), callback == null ? null : new MethodChannel.IncomingResultHandler(callback));
}
@UiThread
public void setMethodCallHandler(@Nullable MethodChannel.MethodCallHandler handler) {
this.messenger.setMessageHandler(this.name, handler == null ? null : new MethodChannel.IncomingMethodCallHandler(handler));
}
private final class IncomingMethodCallHandler implements BinaryMessageHandler {
private final MethodChannel.MethodCallHandler handler;
IncomingMethodCallHandler(MethodChannel.MethodCallHandler handler) {
this.handler = handler;
}
@UiThread
public void onMessage(ByteBuffer message, final BinaryReply reply) {
MethodCall call = MethodChannel.this.codec.decodeMethodCall(message);
try {
this.handler.onMethodCall(call, new MethodChannel.Result() {
public void success(Object result) {
reply.reply(MethodChannel.this.codec.encodeSuccessEnvelope(result));
}
public void error(String errorCode, String errorMessage, Object errorDetails) {
reply.reply(MethodChannel.this.codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails));
}
public void notImplemented() {
reply.reply((ByteBuffer)null);
}
});
} catch (RuntimeException var5) {
Log.e("MethodChannel#" + MethodChannel.this.name, "Failed to handle method call", var5);
reply.reply(MethodChannel.this.codec.encodeErrorEnvelope("error", var5.getMessage(), (Object)null));
}
}
}
private final class IncomingResultHandler implements BinaryReply {
private final MethodChannel.Result callback;
IncomingResultHandler(MethodChannel.Result callback) {
this.callback = callback;
}
@UiThread
public void reply(ByteBuffer reply) {
try {
if (reply == null) {
this.callback.notImplemented();
} else {
try {
this.callback.success(MethodChannel.this.codec.decodeEnvelope(reply));
} catch (FlutterException var3) {
this.callback.error(var3.code, var3.getMessage(), var3.details);
}
}
} catch (RuntimeException var4) {
Log.e("MethodChannel#" + MethodChannel.this.name, "Failed to handle method call result", var4);
}
}
}
public interface Result {
@UiThread
void success(@Nullable Object var1);
@UiThread
void error(String var1, @Nullable String var2, @Nullable Object var3);
@UiThread
void notImplemented();
}
public interface MethodCallHandler {
@UiThread
void onMethodCall(@NonNull MethodCall var1, @NonNull MethodChannel.Result var2);
}
}
iOS 中的 FlutterMethodChannel
@interface FlutterMethodChannel : NSObject
+ (instancetype)methodChannelWithName:(NSString*)name
binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger;
+ (instancetype)methodChannelWithName:(NSString*)name
binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger
codec:(NSObject<FlutterMethodCodec>*)codec;
- (instancetype)initWithName:(NSString*)name
binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger
codec:(NSObject<FlutterMethodCodec>*)codec;
- (void)invokeMethod:(NSString*)method arguments:(id _Nullable)arguments;
- (void)invokeMethod:(NSString*)method
arguments:(id _Nullable)arguments
result:(FlutterResult _Nullable)callback;
- (void)setMethodCallHandler:(FlutterMethodCallHandler _Nullable)handler;
- (void)resizeChannelBuffer:(NSInteger)newSize;
@end
BaseMessageChannel、EventChannel和MethodChannel的使用方式十分类似。
Channel name
可以认为是一个不能重复的键,是作为 channel 的标识来使用的,与原生之间是通过 name 来进行判断的。
BinaryMessager
虽然三种Channel各有用途,但是他们与Flutter通信的工具却是相同的,均为BinaryMessager。
BinaryMessenger 是 Platform 端与Flutter端通信的工具,其通信使用的消息格式为二进制格式数据。当我们初始化一个 Channel,并向该 Channel 注册处理消息的 Handler 时,实际上会生成一个与之对应的 BinaryMessageHandler,并以 channel name 为 key,注册到 BinaryMessenger 中。当Flutter 端发送消息到 BinaryMessenger 时,BinaryMessenger 会根据其入参 channel找到对应的BinaryMessageHandler,并交由其处理。
abstract class BinaryMessenger {
const BinaryMessenger();
Future<void> handlePlatformMessage(String channel, ByteData data, ui.PlatformMessageResponseCallback callback);
Future<ByteData> send(String channel, ByteData message);
void setMessageHandler(String channel, MessageHandler handler);
bool checkMessageHandler(String channel, MessageHandler handler);
bool checkMockMessageHandler(String channel, MessageHandler handler);
}
Binarymessenger 在 Android 端是一个接口,其具体实现为 FlutterNativeView。而其在iOS端是一个协议,名称为 FlutterBinaryMessenger,FlutterViewController 遵循了它。
安卓中的 BinaryMessenger
public interface BinaryMessenger {
@UiThread
void send(@NonNull String var1, @Nullable ByteBuffer var2);
@UiThread
void send(@NonNull String var1, @Nullable ByteBuffer var2, @Nullable BinaryMessenger.BinaryReply var3);
@UiThread
void setMessageHandler(@NonNull String var1, @Nullable BinaryMessenger.BinaryMessageHandler var2);
public interface BinaryReply {
@UiThread
void reply(@Nullable ByteBuffer var1);
}
public interface BinaryMessageHandler {
@UiThread
void onMessage(@Nullable ByteBuffer var1, @NonNull BinaryMessenger.BinaryReply var2);
}
}
iOS 中的 FlutterBinaryMessenger
@protocol FlutterBinaryMessenger <NSObject>
- (void)sendOnChannel:(NSString*)channel message:(NSData* _Nullable)message;
- (void)sendOnChannel:(NSString*)channel
message:(NSData* _Nullable)message
binaryReply:(FlutterBinaryReply _Nullable)callback;
- (void)setMessageHandlerOnChannel:(NSString*)channel
binaryMessageHandler:(FlutterBinaryMessageHandler _Nullable)handler;
@end
Binarymessenger 只和 BinaryMessageHandler 打交道。而 Channel 和 BinaryMessageHandler 则是一一对应的。由于 Channel 从 BinaryMessageHandler 接收到的消息是二进制格式数据,无法直接使用,故 Channel 会将该二进制消息通过 Codec(消息编解码器)解码为能识别的消息并传递给 Handler 进行处理。
当 Handler 处理完消息之后,会通过回调函数返回 result,并将 result 通过编解码器编码为二进制格式数据,通过 BinaryMessenger 发送回 Flutter 端。
Codec
Codec 主要用于将二进制格式的数据转化为 Handler 能够识别的数据,Flutter 定义了两种Codec:MessageCodec 和 MethodCodec。
MessageCodec 提供了 encodeMessage 和 decodeMessage 两个接口,MethodCodec 有多种不同的实现:BinaryCodec、StringCodec、JSONMessageCodec
安卓的代码实现如下:
public interface MessageCodec<T> {
ByteBuffer encodeMessage(T message);
T decodeMessage(ByteBuffer message);
}
iOS的代码实现:
@protocol FlutterMessageCodec
+ (instancetype)sharedInstance;
- (NSData* _Nullable)encode:(id _Nullable)message;
- (id _Nullable)decode:(NSData* _Nullable)message;
@end
MethodCodec 用于二进制数据与方法调用(MethodCall)和返回结果之间的编解码。MethodChannel 和 EventChannel 所使用的编解码器均为 MethodCodec。MethodCodec 提供了对调用结果的处理,MethodCodec 有多种不同的实现:JSONMethodCodec、StandardMethodCodec
安卓的代码实现如下:
public interface MethodCodec {
ByteBuffer encodeMethodCall(MethodCall methodCall);
MethodCall decodeMethodCall(ByteBuffer methodCall);
ByteBuffer encodeSuccessEnvelope(Object result);
ByteBuffer encodeErrorEnvelope(String errorCode, String errorMessage, Object errorDetails);
Object decodeEnvelope(ByteBuffer envelope);
}
iOS的代码实现:
@protocol FlutterMethodCodec
+ (instancetype)sharedInstance;
- (NSData*)encodeMethodCall:(FlutterMethodCall*)methodCall;
- (FlutterMethodCall*)decodeMethodCall:(NSData*)methodCall;
- (NSData*)encodeSuccessEnvelope:(id _Nullable)result;
- (NSData*)encodeErrorEnvelope:(FlutterError*)error;
- (id _Nullable)decodeEnvelope:(NSData*)envelope;
@end
Handler
当接收到二进制格式消息并使用 Codec 将其解码后,就该 Handler 去处理了。Flutter定义了三种类型的 Handler:MessageHandler、MethodHandler 和 StreamHandler,与 Channel 类型一一对应,其实就是键值对应的关系。当向 Channel 注册一个 Handler 时,实际就是向 BinaryMessager 注册一个与之对应的 BinaryMessageHandler。当消息派分到 BinaryMessageHandler 后,Channel 会通过 Codec 将消息解码,并传递给 Handler 处理。
消息传递过程
Dart & Flutter 层处理
当我们在 Flutter 端使用 MethodChannel 的 invokeMethod 方法发起一次方法调用时,就开始了我们的消息传递之旅。invokeMethod 方法会将其入参 message 和 arguments 封装成一个 MethodCall 对象,并使用 MethodCodec 将其编码为二进制格式数据,再通过 BinaryMessages 将消息发出。
底层引擎处理
该过程是在引擎底层处理的,window.cc 的 SendPlatformMessage 方法接收了来自dart层的三个参数,并对它们做了一定的处理:
- dart层的回调 callback 封装为 native 层的 PlatformMessageResponseDart 类型的response;
- dart 层的二进制数据 data 转化为 std::vector
类型数据 data; - 根据 response, data 以及 Channel 名称 name 创建一个 PlatformMessage 对象,并通过dart_state->window()->client()->HandlePlatformMessage 方法处理 PlatformMessage 对象。
dart_state->window()->client() 是一个WindowClient,而其具体的实现为 RuntimeController,RuntimeController 会将消息交给其代理 RuntimeDelegate 处理。RuntimeDelegate 的实现为 Engine, Engine 在处理 Message 时,会判断该消息是否是为了获取资源( channel 等于"flutter/assets"),如果是,则走获取资源逻辑,否则调用 Engine::Delegate 的OnEngineHandlePlatformMessage 方法。
Engine::Delegate 的具体实现为 Shell,其 OnEngineHandlePlatformMessage 接收到消息后,会向 PlatformTaskRunner 添加一个 Task,该 Task 会调用 PlatformView 的 HandlePlatformMessage 方法。值得注意的是,Task 中的代码执行在 Platform Task Runner 中,而之前的代码均执行在 UI Task Runner中。
原生处理
PlatformView 的 HandlePlatformMessage 方法在不同平台有不同的实现,但是其基本原理是相同的。移动端平台主要是 PlatformViewIOS、PlatformViewAndroid。
结果回传
PlatformMessageResponseDart 的 Complete 方法向 UI Task Runner 添加了一个新的 Task,这个 Task 的作用是将二进制结果从 native 的二进制数据类型转化为 Dart 的二进制数据类型 response,并调用 dart 的 callback 将 response 传递到 Dart 层。 Dart 层接收到二进制数据后,使用 MethodCodec 将数据解码,并返回给业务层。至此,一次从Flutter 发起的方法调用就完整结束了。
更加完整的过程可以去咸鱼公众号查看哦~