Flutter作为当前最活跃的跨渠道结构在不同渠道通讯都是经过Platform Channel进行的。Channel的数据传递的规矩是什么呢、传递的流程是什么、在数据传递中是否存在数据的复制?咱们经过调试源码的办法来探究一下。

Platform Channel的类型

  • BasicMessageChannel:用于数据的传递,渠道和dart能够互相传递。
  • MethodChannel:用于传递办法调用,渠道和dart可互相调用办法,这个也是咱们在项目里用的最多的。
  • EventChannel:用来进行数据流的通讯,建立链接后渠道发送音讯,dart侧接收音讯。

本文的调试主要是依据MethodChannel。

FlutterChannel的整体架构

Flutter Platform Channel源码浅析

Channel的数据传递的规矩

咱们看channel的构造办法是会看到有三个参数:

  • name:channel姓名,用来不同渠道进行呼应的一个标识,String类型。
  • codec:音讯的编解码器,音讯经过这个对象进行发送编码或接收解码,MethodCodec类型。
  • binaryMessenger:信使,用于发送数据,BinaryMessenger类型。

类的联系

Flutter Platform Channel源码浅析

传递的数据格局

由于binaryMessenger是发送数据的信使,所以咱们看一下BinaryMessenger类的办法:

  /// Send a binary message to the platform plugins on the given channel.
  /// Returns a [Future] which completes to the received response, undecoded,
  /// in binary form.
  Future<ByteData?>? send(String channel, ByteData? message);

能够看出send办法的入参和返回值都是ByteData类型,也便是二进制数据流,到这儿咱们知道channel在不同渠道传递的进程中的数据格局是二进制流,这一点原理非常相似依据二进制协议开发的网络服务。

数据的编解码

Flutter中有两种Codec:

  • MessageCodec: 用于二进制和根底数据类型的编解码,BasicMessageChannel运用的是MessageCodec(默认运用StandardMessageCodec)。
  • MethodCodec:用于二进制数据与办法的调用和返回成果之间的编解码,主要用于MethodChannel和EventChannel(默认运用StandardMethodCodec)。

由于StandardMethodCodec也是经过StandardMessageCodec的writeValue和readValue办法,所以咱们只需求看StandardMessageCodec的编解码进程。

支撑的数据类型

  static const int _valueNull = 0;
  static const int _valueTrue = 1;
  static const int _valueFalse = 2;
  static const int _valueInt32 = 3;
  static const int _valueInt64 = 4;
  static const int _valueLargeInt = 5;
  static const int _valueFloat64 = 6;
  static const int _valueString = 7;
  static const int _valueUint8List = 8;
  static const int _valueInt32List = 9;
  static const int _valueInt64List = 10;
  static const int _valueFloat64List = 11;
  static const int _valueList = 12;
  static const int _valueMap = 13;
  static const int _valueFloat32List = 14;

看到StandardMessageCodec类中定义了支撑编解码的数据类型。能够看出都是基本数据类型。

数据的编码

编码调用的办法是writeValue:

///写入二进制数据
void writeValue(WriteBuffer buffer, Object? value) {
    if (value == null) {
      //空类型
      buffer.putUint8(_valueNull);
    } else if (value is bool) {
      //布尔类型
      buffer.putUint8(value ? _valueTrue : _valueFalse);
    } else if (value is double) {  // Double precedes int because in JS everything is a double.
                                   // Therefore in JS, both `is int` and `is double` always
                                   // return `true`. If we check int first, we'll end up treating
                                   // all numbers as ints and attempt the int32/int64 conversion,
                                   // which is wrong. This precedence rule is irrelevant when
                                   // decoding because we use tags to detect the type of value.
      buffer.putUint8(_valueFloat64);
      buffer.putFloat64(value);
    } else if (value is int) { // ignore: avoid_double_and_int_checks, JS code always goes through the `double` path above
      if (-0x7fffffff - 1 <= value && value <= 0x7fffffff) {
        buffer.putUint8(_valueInt32);
        buffer.putInt32(value);
      } else {
        buffer.putUint8(_valueInt64);
        buffer.putInt64(value);
      }
    } else if (value is String) {
      buffer.putUint8(_valueString);
      final Uint8List bytes = utf8.encoder.convert(value);
      writeSize(buffer, bytes.length);
      buffer.putUint8List(bytes);
    } else if (value is Uint8List) {
      buffer.putUint8(_valueUint8List);
      writeSize(buffer, value.length);
      buffer.putUint8List(value);
    } else if (value is Int32List) {
      buffer.putUint8(_valueInt32List);
      writeSize(buffer, value.length);
      buffer.putInt32List(value);
    } else if (value is Int64List) {
      buffer.putUint8(_valueInt64List);
      writeSize(buffer, value.length);
      buffer.putInt64List(value);
    } else if (value is Float32List) {
      buffer.putUint8(_valueFloat32List);
      writeSize(buffer, value.length);
      buffer.putFloat32List(value);
    } else if (value is Float64List) {
      buffer.putUint8(_valueFloat64List);
      writeSize(buffer, value.length);
      buffer.putFloat64List(value);
    } else if (value is List) {
      //写数据类型标识
      buffer.putUint8(_valueList);
      //记录数据的巨细
      writeSize(buffer, value.length);
      for (final Object? item in value) {
        writeValue(buffer, item);
      }
    } else if (value is Map) {
      buffer.putUint8(_valueMap);
      writeSize(buffer, value.length);
      value.forEach((Object? key, Object? value) {
        writeValue(buffer, key);
        writeValue(buffer, value);
      });
    } else {
      throw ArgumentError.value(value);
    }
  }
  ///写数据巨细
  void writeSize(WriteBuffer buffer, int value) {
      assert(0 <= value && value <= 0xffffffff);
      if (value < 254) {
        // 只需求写8位
        buffer.putUint8(value);
      } else if (value <= 0xffff) {
        // 前8位值为254 标识后16位数据长度
        buffer.putUint8(254);
        buffer.putUint16(value);
      } else {
        // 前8位值为254 标识后32位数据长度
        buffer.putUint8(255);
        buffer.putUint32(value);
      }
}

一个method调用的数据的二进制格局如下:

Flutter Platform Channel源码浅析

数据类型是固定8位,数据长度的位数不一定,它的规矩和数据的封装很相似:

Flutter Platform Channel源码浅析

  • null、bool类型

这两种类型都是直接8个字节的标识位直接表明值,及null类型传值为00000000,bool的true的值为 00000001,false的值为00000010,这个规划还是很巧妙的,只需求一个标识位就能够区分出null、true、false的值,不需求剩余的字节存储数据的值。

  • double类型

前8位表明数据类型00000110,后边64位是详细的值。

  • int 类型

int32: 前8位表明数据类型00000011,后边32位是详细的值。

int64:前8位表明数据类型00000100,后边64位是详细的值。

  • String类型

前8位表明数据类型00000111,中心8/16/32位表明数据String转换成Uint8List类型的值的长度,详细多少位和字符串长度相关,最大支撑 2^32的长度,最后是将String转换成Uint8List类型的值。

  • Uint8List、Int32List、Int64List、Float32List、Float64List

这几种类型处理办法是共同的前8位表明数据类型,中心表明数据的长度,最后是详细数据的值。

  • List

前8位表明数据类型,然后依次遍历数组元素递归调用writeValue重复前面类型的判别和写入。

  • Map

前8位表明数据类型,和数组相似遍历Map中元素key和value分别调用writeValue递归调用写入。

能够看出大部分数据类型都是标志位+数据长度+数据值这种办法编码的,List、Map中有循环遍历其间每个元素进行编码。

数据的解码

解码的办法是readValue,它实践调用readValueOfType进行详细的解码:

Object? readValue(ReadBuffer buffer) {
    if (!buffer.hasRemaining)
      throw const FormatException('Message corrupted');
          //获取数据的前8个字节 存储的数据类型
    final int type = buffer.getUint8();
    return readValueOfType(type, buffer);
  }
 Object? readValueOfType(int type, ReadBuffer buffer) {
    switch (type) {
      case _valueNull:
        return null;
      case _valueTrue:
        return true;
      case _valueFalse:
        return false;
      case _valueInt32:
        return buffer.getInt32();
      case _valueInt64:
        return buffer.getInt64();
      case _valueFloat64:
        return buffer.getFloat64();
      case _valueLargeInt:
      case _valueString:
        final int length = readSize(buffer);
        return utf8.decoder.convert(buffer.getUint8List(length));
      case _valueUint8List:
        final int length = readSize(buffer);
        return buffer.getUint8List(length);
      case _valueInt32List:
        final int length = readSize(buffer);
        return buffer.getInt32List(length);
      case _valueInt64List:
        final int length = readSize(buffer);
        return buffer.getInt64List(length);
      case _valueFloat32List:
        final int length = readSize(buffer);
        return buffer.getFloat32List(length);
      case _valueFloat64List:
        final int length = readSize(buffer);
        return buffer.getFloat64List(length);
      case _valueList:
        final int length = readSize(buffer);
        final List<Object?> result = List<Object?>.filled(length, null);
        for (int i = 0; i < length; i++)
          result[i] = readValue(buffer);
        return result;
      case _valueMap:
        final int length = readSize(buffer);
        final Map<Object?, Object?> result = <Object?, Object?>{};
        for (int i = 0; i < length; i++)
          result[readValue(buffer)] = readValue(buffer);
        return result;
      default: throw const FormatException('Message corrupted');
    }
 }
  • 首要是经过buffer.getUint8()获取到数据的实践类型。
  • null、true、false:直接返回值。
  • int32、int64、float:获取后边的32或64位的值。
  • String :先获取巨细,然后utf8编码将uint8List转换成String。
  • Uint8List、Int32List、Int64List、Float32List、Float64List:先获取巨细,依据巨细获取对应位数的数据值。
  • List:先获取数据巨细,然后运用循环句子将数据逐个写入数组。
  • Map:先获取数据巨细,然后运用循环句子将数据逐个写入Map。

Channel的调用流程

研讨调用流程最好的办法是依据断点一步步跟进,由于channel的调用规划到engine代码的调用,所以咱们需求下载和编译Flutter的引擎源码(需求科学上网)。

下载编译引擎代码

东西的预备

  • Chromium提供的部署东西depot_tools,履行下面命令:
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
  • 装置ant
brew install ant

下载引擎

  • 新建目录 路径姓名能够自定义
mkdir engine
  • 创立gclient文件
touch .gclient
  • 修改gclient文件 这儿有个留意的点,commitID一定要和本地flutter里的engine共同,我这儿运用的是3.0.5的版别。
solutions = [
    {
        "managed": False,
        "name": "src/flutter",
        "url": "git@github.com:flutter/engine.git@e85ea0e79c6d894c120cda4ee8ee10fe6745e187",
        "custom_deps": {},
        "deps_file": "DEPS",
        "safesync_url": "",
    },
]
  • 履行gclient sync ,这个命令会很耗时,假如中止报错能够从头履行会继续下载。
gclient sync

编译引擎代码

  • 构建 以iOS设备运用的引擎为例,cd到engine/src/flutter/tools目录

Flutter Platform Channel源码浅析

#构建iOS设备运用的引擎
#真机debug版别
./gn --ios --unoptimized
#真机release版别
./gn --ios --unoptimized --runtime-mode=release
#模拟器版别
./gn --ios --simulator --unoptimized
#主机端(Mac)构建
./gn --unoptimized
  • 编译 cd到engine/src/out目录

Flutter Platform Channel源码浅析

ninja -C host_debug_unopt && ninja -C ios_debug_sim_unopt && ninja -C ios_debug_unopt && ninja -C ios_release_unopt

调用流程

编译好engine代码后,咱们在demo工程里指定engine运用咱们本地的引擎就能够运用Xcode调试Channel的调用流程了。

发送数据

  • channel的调用是经过invokeMethod办法建议的
- (void)invokeMethod:(NSString*)method arguments:(id)arguments result:(FlutterResult)callback {
  FlutterMethodCall* methodCall = [FlutterMethodCall methodCallWithMethodName:method
                                                                    arguments:arguments];
  NSData* message = [_codec encodeMethodCall:methodCall];
  FlutterBinaryReply reply = ^(NSData* data) {
    if (callback) {
      callback((data == nil) ? FlutterMethodNotImplemented : [_codec decodeEnvelope:data]);
    }
  };
  [_messenger sendOnChannel:_name message:message binaryReply:reply];
}

发送音讯经过调用_messenge(信使)的sendOnChannel办法。

  • FlutterEngine.mm文件的sendOnChannel办法
- (void)sendOnChannel:(NSString*)channel
              message:(NSData*)message
          binaryReply:(FlutterBinaryReply)callback {
  NSParameterAssert(channel);
  NSAssert(_shell && _shell->IsSetup(),
           @"Sending a message before the FlutterEngine has been run.");
  fml::RefPtr<flutter::PlatformMessageResponseDarwin> response =
      (callback == nil) ? nullptr                         : fml::MakeRefCounted<flutter::PlatformMessageResponseDarwin>(
                              ^(NSData* reply) {
                                //这儿是dart返回回调
                                callback(reply);
                              },
                              _shell->GetTaskRunners().GetPlatformTaskRunner());
  std::unique_ptr<flutter::PlatformMessage> platformMessage =
      (message == nil) ? std::make_unique<flutter::PlatformMessage>(channel.UTF8String, response)
                       : std::make_unique<flutter::PlatformMessage>(
                             channel.UTF8String, flutter::CopyNSDataToMapping(message), response);
  _shell->GetPlatformView()->DispatchPlatformMessage(std::move(platformMessage));
  // platformMessage takes ownership of response.
  // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
}

能够看到这个办法里调用了CopyNSDataToMapping(message)也便是对数据进行了一次复制,对数据复制之后调用了PlatformView的DispatchPlatformMessage办法。

  • platform_view.cc 文件 PlatformView::DispatchPlatformMessage办法
void PlatformView::DispatchPlatformMessage(
    std::unique_ptr<PlatformMessage> message) {
  delegate_.OnPlatformViewDispatchPlatformMessage(std::move(message));
}

这个办法履行了Shell::OnPlatformViewDispatchPlatformMessage。

  • shell.cc 文件 Shell::OnPlatformViewDispatchPlatformMessage办法
// |PlatformView::Delegate|
void Shell::OnPlatformViewDispatchPlatformMessage(
    std::unique_ptr<PlatformMessage> message) {
  FML_DCHECK(is_setup_);
  FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
  ///获取UI线程
  task_runners_.GetUITaskRunner()->PostTask(fml::MakeCopyable(
      [engine = engine_->GetWeakPtr(), message = std::move(message)]() mutable {
        if (engine) {
          engine->DispatchPlatformMessage(std::move(message));
        }
      }));
}

切换线程前

Flutter Platform Channel源码浅析

切换线程后

Flutter Platform Channel源码浅析

结合debug信息咱们能够得出定论,这个办法从原生主线程切换到了Flutter的UI线程。

然后在UI线程里调用用engine->DispatchPlatformMessage。

  • engine.cc文件 Engine::DispatchPlatformMessage办法
void Engine::DispatchPlatformMessage(std::unique_ptr<PlatformMessage> message) {
  std::string channel = message->channel();
  if (channel == kLifecycleChannel) {
    if (HandleLifecyclePlatformMessage(message.get())) {
      return;
    }
  } else if (channel == kLocalizationChannel) {
    if (HandleLocalizationPlatformMessage(message.get())) {
      return;
    }
  } else if (channel == kSettingsChannel) {
    HandleSettingsPlatformMessage(message.get());
    return;
  } else if (!runtime_controller_->IsRootIsolateRunning() &&
             channel == kNavigationChannel) {
    // If there's no runtime_, we may still need to set the initial route.
    HandleNavigationPlatformMessage(std::move(message));
    return;
  }
  //判别是否在主isolate也便是前一个办法提到的UI线程
  if (runtime_controller_->IsRootIsolateRunning() &&
      runtime_controller_->DispatchPlatformMessage(std::move(message))) {
    return;
  }
  FML_DLOG(WARNING) << "Dropping platform message on channel: " << channel;
}

能够看到这儿会有几个结构层的channel会有专门的的Handle进行呼应,咱们自定义的channel会调用到runtime_controller_->DispatchPlatformMessage,留意这儿必须是在主isolate的时分才会调用。

  • runntime_controller.cc 文件 RuntimeController::DispatchPlatformMessage
bool RuntimeController::DispatchPlatformMessage(
    std::unique_ptr<PlatformMessage> message) {
  if (auto* platform_configuration = GetPlatformConfigurationIfAvailable()) {
    TRACE_EVENT1("flutter", "RuntimeController::DispatchPlatformMessage",
                 "mode", "basic");
    platform_configuration->DispatchPlatformMessage(std::move(message));
    return true;
  }
  return false;
}

这个办法依然是在dart的主isolate履行,调用了platform_configuration->DispatchPlatformMessage(std::move(message))。

  • platform_configuration.cc 文件 PlatformConfiguration::DispatchPlatformMessage
void PlatformConfiguration::DispatchPlatformMessage(
    std::unique_ptr<PlatformMessage> message) {
  std::shared_ptr<tonic::DartState> dart_state =
      dispatch_platform_message_.dart_state().lock();
  if (!dart_state) {
    FML_DLOG(WARNING)
        << "Dropping platform message for lack of DartState on channel: "
        << message->channel();
    return;
  }
  tonic::DartState::Scope scope(dart_state);
  ///数据进行一次复制
  Dart_Handle data_handle =
      (message->hasData()) ? ToByteData(message->data()) : Dart_Null();
  if (Dart_IsError(data_handle)) {
    FML_DLOG(WARNING)
        << "Dropping platform message because of a Dart error on channel: "
        << message->channel();
    return;
  }
  int response_id = 0;
  if (auto response = message->response()) {
    response_id = next_response_id_++;
    pending_responses_[response_id] = response;
  }
  /// dispatch_platform_message_获取message参数
  tonic::LogIfError(
      tonic::DartInvoke(dispatch_platform_message_.Get(),
                        {tonic::ToDart(message->channel()), data_handle,
                         tonic::ToDart(response_id)}));
}
/// message set的代码
  dispatch_platform_message_.Set(
      tonic::DartState::Current(),
      Dart_GetField(library, tonic::ToDart("_dispatchPlatformMessage")));

这个办法首要对数据进行了一次复制,然后调用到了dart层,DartInvoke是Dart VM提供了CPP调用Dart的能力其间一个办法。

接收数据

从DartInvoke调用的第一个参数能够看出调用到dart的代码是hook.dart的 _dispatchPlatformMessage。

  • hook.dart 文件 _dispatchPlatformMessage
@pragma('vm:entry-point')
void _dispatchPlatformMessage(String name, ByteData? data, int responseId) {
  PlatformDispatcher.instance._dispatchPlatformMessage(name, data, responseId);
}

调用了PlatformDispatcher的 _dispatchPlatformMessage办法。

  • platform_dispatcher.dart 文件 _dispatchPlatformMessage
void _dispatchPlatformMessage(String name, ByteData? data, int responseId) {
  if (name == ChannelBuffers.kControlChannelName) {
    ///系统的channelname
    try {
      channelBuffers.handleMessage(data!);
    } finally {
      _respondToPlatformMessage(responseId, null);
    }
  } else if (onPlatformMessage != null) {
    _invoke3<String, ByteData?, PlatformMessageResponseCallback>(
      onPlatformMessage,
      _onPlatformMessageZone,
      name,
      data,
      (ByteData? responseData) {
       /// 处理完数据的回调
        _respondToPlatformMessage(responseId, responseData);
      },
    );
  } else {
    channelBuffers.push(name, data, (ByteData? responseData) {
      _respondToPlatformMessage(responseId, responseData);
    });
  }
}
/// 运用zone调用 callback
void _invoke3<A1, A2, A3>(void Function(A1 a1, A2 a2, A3 a3)? callback, Zone zone, A1 arg1, A2 arg2, A3 arg3) {
  if (callback == null) {
    return;
  }
  assert(zone != null);
  if (identical(zone, Zone.current)) {
    callback(arg1, arg2, arg3);
  } else {
    zone.runGuarded(() {
      callback(arg1, arg2, arg3);
    });
  }
}

_invoke3办法实践是调用onPlatformMessage。

  • binding.dart handlePlatformMessage办法
Future<void> handlePlatformMessage(
  String channel,
  ByteData? message,
  ui.PlatformMessageResponseCallback? callback,
) async {
  ui.channelBuffers.push(channel, message, (ByteData? data) {
    if (callback != null)
      callback(data);
  });
}

将传过来的channel添加的channel缓存池,等待消耗缓存池的当地履行对应的办法。

呼应channel

呼应的办法便是咱们在写 methodChannel.setMethodCallHandler里,它实践调用的是binaryMessage的setMessageHandler。

  • binding.dart 文件 setMessageHandler
  @override
  void setMessageHandler(String channel, MessageHandler? handler) {
    if (handler == null) {
      ui.channelBuffers.clearListener(channel);
    } else {
      ui.channelBuffers.setListener(channel, (ByteData? data, ui.PlatformMessageResponseCallback callback) async {
        ByteData? response;
        try {
          /// codec对数据进行解码 处理 返回
          response = await handler(data);
        } catch (exception, stack) {
          FlutterError.reportError(FlutterErrorDetails(
            exception: exception,
            stack: stack,
            library: 'services library',
            context: ErrorDescription('during a platform message callback'),
          ));
        } finally {
          /// 调用回调 engine层
          callback(response);
        }
      });
    }
  }
}

这个便是对channelBuffers添加监听,假如有channel就会调用handler也便是codec的_handleAsMethodCall办法,await之后拿到返回值调用回调到platform_dispatcher的_respondToPlatformMessage。

  • platform_dispatcher.dart 的 _respondToPlatformMessage
 /// Called by [ _dispatchPlatformMessage].
void _respondToPlatformMessage(int responseId, ByteData? data)
    native 'PlatformConfiguration_PlatformConfiguration';

这个办法便是回调了c++层代码PlatformConfiguration._respondToPlatformMessage。

  • platform_configuration.cc的 RespondToPlatformMessage办法
void _RespondToPlatformMessage(Dart_NativeArguments args) {
  tonic::DartCallStatic(&RespondToPlatformMessage, args);
}
void RespondToPlatformMessage(Dart_Handle window,
                              int response_id,
                              const tonic::DartByteData& data) {
  if (Dart_IsNull(data.dart_handle())) {
    UIDartState::Current()
        ->platform_configuration()
        ->CompletePlatformMessageEmptyResponse(response_id);
  } else {
    // TODO(engine): Avoid this copy.(这儿有一个复制)
    const uint8_t* buffer = static_cast<const uint8_t*>(data.data());
    UIDartState::Current()
        ->platform_configuration()
        ->CompletePlatformMessageResponse(
            response_id,
            std::vector<uint8_t>(buffer, buffer + data.length_in_bytes()));
  }
}
  • platform_configuration.cc的CompletePlatformMessageResponse办法
void PlatformConfiguration::(
    int response_id,
    std::vector<uint8_t> data) {
  if (!response_id) {
    return;
  }
  auto it = pending_responses_.find(response_id);
  if (it == pending_responses_.end()) {
    return;
  }
  auto response = std::move(it->second);
  pending_responses_.erase(it);
  response->Complete(std::make_unique<fml::DataMapping>(std::move(data)));
}
  • platform_message_response_darwin.m的Complete办法
void PlatformMessageResponseDarwin::Complete(std::unique_ptr<fml::Mapping> data) {
  fml::RefPtr<PlatformMessageResponseDarwin> self(this);
  platform_task_runner_->PostTask(fml::MakeCopyable([self, data = std::move(data)]() mutable {
    self->callback_.get()(CopyMappingPtrToNSData(std::move(data)));//数据有一次复制
  }));
}

有dart的UI线程切换到主线程履行回调,这儿返回数据进行了一次复制,后边的调用便是找到callback返回到Invoke办法建议的当地。

调用时序图

综合剖析源码咱们能够得到一个iOS端建议一个channel恳求到收到回调的一个时序图如下

Flutter Platform Channel源码浅析

总结

本文咱们主要研讨了Flutter Channel由建议到回调的一整个流程,经过剖析咱们能够得到下面的定论

  • channel在传输进程中的数据格局为二进制数据流,这和依据二进制传输的网络层相似。
  • channel支撑传输的数据类型有null、bool、int、double、String、Uint8List、Int32List、Int64List、Float64List、List、Map。
  • 数据在传输进程中进行了至少两次复制,回调进程中数据也是至少两次复制。