Flutter作为当前最活跃的跨渠道结构在不同渠道通讯都是经过Platform Channel进行的。Channel的数据传递的规矩是什么呢、传递的流程是什么、在数据传递中是否存在数据的复制?咱们经过调试源码的办法来探究一下。
Platform Channel的类型
- BasicMessageChannel:用于数据的传递,渠道和dart能够互相传递。
- MethodChannel:用于传递办法调用,渠道和dart可互相调用办法,这个也是咱们在项目里用的最多的。
- EventChannel:用来进行数据流的通讯,建立链接后渠道发送音讯,dart侧接收音讯。
本文的调试主要是依据MethodChannel。
FlutterChannel的整体架构
Channel的数据传递的规矩
咱们看channel的构造办法是会看到有三个参数:
- name:channel姓名,用来不同渠道进行呼应的一个标识,String类型。
- codec:音讯的编解码器,音讯经过这个对象进行发送编码或接收解码,MethodCodec类型。
- binaryMessenger:信使,用于发送数据,BinaryMessenger类型。
类的联系
传递的数据格局
由于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调用的数据的二进制格局如下:
数据类型是固定8位,数据长度的位数不一定,它的规矩和数据的封装很相似:
- 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目录
#构建iOS设备运用的引擎
#真机debug版别
./gn --ios --unoptimized
#真机release版别
./gn --ios --unoptimized --runtime-mode=release
#模拟器版别
./gn --ios --simulator --unoptimized
#主机端(Mac)构建
./gn --unoptimized
- 编译 cd到engine/src/out目录
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));
}
}));
}
切换线程前
切换线程后
结合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 Channel由建议到回调的一整个流程,经过剖析咱们能够得到下面的定论
- channel在传输进程中的数据格局为二进制数据流,这和依据二进制传输的网络层相似。
- channel支撑传输的数据类型有null、bool、int、double、String、Uint8List、Int32List、Int64List、Float64List、List、Map。
- 数据在传输进程中进行了至少两次复制,回调进程中数据也是至少两次复制。