Flutter 与Native交互

标签: 编程学习 Flutter学习

前面我们介绍过如何写一个调用原生 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,它们分别是

  1. BasicMessageChannel:用于传递字符串和半结构化的信息。
  2. MethodChannel:用于传递方法调用(method invocation)。
  3. EventChannel: 用于数据流(event streams)的通信。

三种Channel之间互相独立,各有用途,但它们在设计上却非常相近。每种Channel均有三个重要成员变量:

  1. name: String类型,代表Channel的名字,也是其唯一标识符。
  2. messager:BinaryMessenger类型,代表消息信使,是消息的发送与接收的工具。
  3. 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层的三个参数,并对它们做了一定的处理:

  1. dart层的回调 callback 封装为 native 层的 PlatformMessageResponseDart 类型的response;
  2. dart 层的二进制数据 data 转化为 std::vector 类型数据 data;
  3. 根据 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 发起的方法调用就完整结束了。

更加完整的过程可以去咸鱼公众号查看哦~