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。
- 数据在传输过程中进行了至少两次拷贝,回调过程中数据也是至少两次拷贝。