从dart如何调用skia绘图说起
前段时间面试某大厂,面试官问了一个问题,dart 是怎么调用skia进行绘图的?
这个问题我一开始想应该是通过类似dartFFI的机制吧,但dart2.5之前没有dartffi ,而flutter可是一开始就是用skia绘图的,flutter不可能采用类似RN这种序列化的方式传递数据, 于是猜测可能是用 libffi(JS调C函数可以使用)实现的,便回答道是libffi?
然后面试官追问到,那 libffi 是内存拷贝还是直接赋值呢?这可算是问到我的知识盲区了,只好回答说,不太清楚,应该是拷贝吧,毕竟dartVM 和原生代码是运行在不同的内存空间中。然后这个问题就结束了。。。
带着疑问,我重新翻阅了一下dart和flutter的源码,重新回顾了一下dart与原生交互的过程, 于是便有了这篇文章。
本文所引用的源码来自于
https://github.com/flutter/engine
https://dart.googlesource.com/sdk/
dart与原生的底层通信机制
我们知道在dartFFI出来之前, dart调用原生需要借助C这一层做中转,类似于JNI,必须有一个映射关系,那么映射关系在哪里建立的呢?我们从最简单的dart向原生发消息看起。
dart调用原生消息主要有三种方式:
- BasicMessageChannel:用于传递字符串和半结构化的信息。
- MethodChannel:用于传递方法调用(method invocation)。
- EventChannel: 用于数据流(event streams)的通信。
借用一张别人的图
image.png
其实这里有两个步骤,首先是dart调用C,然后才是通过JNI 调用Java,这里我们只看dart调用C这部分
一、从dart侧发送消息
根据前面的图,三种通道最终都会走到BinaryMessager
里面来,我们搜索BinaryMessager
找到其默认实现类_DefaultBinaryMessenger
,看下他的send方法
@override
Future<ByteData> send(String channel, ByteData message) {
final MessageHandler handler = _mockHandlers[channel];
if (handler != null)
return handler(message);
return _sendPlatformMessage(channel, message);
}
Future<ByteData> _sendPlatformMessage(String channel, ByteData message) {
final Completer<ByteData> completer = Completer<ByteData>();
// ui.window is accessed directly instead of using ServicesBinding.instance.window
// because this method might be invoked before any binding is initialized.
// This issue was reported in #27541. It is not ideal to statically access
// ui.window because the Window may be dependency injected elsewhere with
// a different instance. However, static access at this location seems to be
// the least bad option.
ui.window.sendPlatformMessage(channel, message, (ByteData reply) {
try {
completer.complete(reply);
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context: ErrorDescription('during a platform message response callback'),
));
}
});
return completer.future;
}
_sendPlatformMessage方法里面调用了ui.window.sendPlatformMessage方法,这个方法的具体实现在engine-main/lib/ui/platform_dispatcher.dart
void sendPlatformMessage(String name, ByteData? data, PlatformMessageResponseCallback? callback) {
final String? error =
_sendPlatformMessage(name, _zonedPlatformMessageResponseCallback(callback), data);
if (error != null)
throw Exception(error);
}
String? _sendPlatformMessage(String name, PlatformMessageResponseCallback? callback, ByteData? data)
native 'PlatformConfiguration_sendPlatformMessage';
这里调用了_sendPlatformMessage,并且我们看到这么一句话native 'PlatformConfiguration_sendPlatformMessage';
,这意味着_sendPlatformMessage会通过PlatformConfiguration_sendPlatformMessage 这个名称映射到原生的对应方法,那具体是怎么映射的呢?还是通过搜索关键字的方法,我们在engine-main/lib/ui/window/platform_configuration.cc 里面找到了方法注册的地方。
void PlatformConfiguration::RegisterNatives(
tonic::DartLibraryNatives* natives) {
natives->Register({
{"PlatformConfiguration_defaultRouteName", DefaultRouteName, 1, true},
{"PlatformConfiguration_scheduleFrame", ScheduleFrame, 1, true},
{"PlatformConfiguration_sendPlatformMessage", _SendPlatformMessage, 4,
true},
{"PlatformConfiguration_respondToPlatformMessage",
_RespondToPlatformMessage, 3, true},
{"PlatformConfiguration_render", Render, 3, true},
{"PlatformConfiguration_updateSemantics", UpdateSemantics, 2, true},
{"PlatformConfiguration_setIsolateDebugName", SetIsolateDebugName, 2,
true},
{"PlatformConfiguration_setNeedsReportTimings", SetNeedsReportTimings, 2,
true},
{"PlatformConfiguration_getPersistentIsolateData",
GetPersistentIsolateData, 1, true},
{"PlatformConfiguration_computePlatformResolvedLocale",
_ComputePlatformResolvedLocale, 2, true},
});
}
这是通过DartLibraryNatives这个类保存了PlatformConfiguration_sendPlatformMessage等方法的签名和实现,重点来了!这里面还有scheduleFrame
、respondToPlatformMessage
等方法,这些都是很涉及到页面刷新(dart调用skia绘图就是用的这个方法),原生调dart方法的回调等重要的方法。
这篇文章中说到Flutter页面渲染过程中dart framework通过调用scheduleFrame来通知Engine需要更新UI,如果是采用类似RN那种把需要绘制的节点信息通过JS 引擎序列化后传递到原生,那效率势必很低,那Flutter是怎么做的呢?带着这个疑问,我继续看了下源码。
二、dartVM底层通信机制
我们进入engine-main/third_party/tonic/dart_library_natives.cc可以看到这么几个方法:
void DartLibraryNatives::Register(std::initializer_list<Entry> entries) {
for (const Entry& entry : entries) {
symbols_.emplace(entry.native_function, entry.symbol);
entries_.emplace(entry.symbol, entry);
}
}
Dart_NativeFunction DartLibraryNatives::GetNativeFunction(
Dart_Handle name,
int argument_count,
bool* auto_setup_scope) {
std::string name_string = StdStringFromDart(name);
auto it = entries_.find(name_string);
if (it == entries_.end())
return nullptr;
const Entry& entry = it->second;
if (entry.argument_count != argument_count)
return nullptr;
*auto_setup_scope = entry.auto_setup_scope;
return entry.native_function;
}
const uint8_t* DartLibraryNatives::GetSymbol(
Dart_NativeFunction native_function) {
auto it = symbols_.find(native_function);
if (it == symbols_.end())
return nullptr;
return reinterpret_cast<const uint8_t*>(it->second);
}
其中GetNativeFunction是获取函数的实现,GetSymbol是获取函数的符号(也就是方法签名),这个就是C与dart绑定的关键了,相当于C里面的函数指针和函数名。
在engine-main/shell/platform/fuchsia/dart_runner/service_isolate.cc 这个文件里面我们可以看到CreateServiceIsolate方法内部调用了GetSymbol和GetNativeFunction
Dart_Isolate CreateServiceIsolate(
const char* uri,
Dart_IsolateFlags* flags_unused, // These flags are currently unused
char** error) {
Dart_SetEmbedderInformationCallback(EmbedderInformationCallback);
const uint8_t *vmservice_data = nullptr, *vmservice_instructions = nullptr;
......//省略无关代码
Dart_Handle library =
Dart_LookupLibrary(Dart_NewStringFromCString("dart:vmservice_io"));
SHUTDOWN_ON_ERROR(library);
Dart_Handle result = Dart_SetRootLibrary(library);
SHUTDOWN_ON_ERROR(result);
result = Dart_SetNativeResolver(library, GetNativeFunction, GetSymbol);//绑定函数的实现和名称
SHUTDOWN_ON_ERROR(result);
......//省略无关代码
}
这里通过Dart_SetNativeResolver将函数名称、函数实现绑定到了dartVM中,跟到这里你会发现engine的代码里面搜索不到Dart_SetNativeResolver这个方法的实现了,是不是就没办法跟下去了呢? 并不是的,Dart_SetNativeResolver这个方法的实现是在dart的源码里,我们可以看到sdk/runtime/vm/dart_api_impl.cc 里面有他的实现。
DART_EXPORT Dart_Handle
Dart_SetNativeResolver(Dart_Handle library,
Dart_NativeEntryResolver resolver,
Dart_NativeEntrySymbol symbol) {
DARTSCOPE(Thread::Current());
const Library& lib = Api::UnwrapLibraryHandle(Z, library);
if (lib.IsNull()) {
RETURN_TYPE_ERROR(Z, library, Library);
}
lib.set_native_entry_resolver(resolver);
lib.set_native_entry_symbol_resolver(symbol);
return Api::Success();
}
sdk/runtime/vm/object.h
void set_native_entry_resolver(Dart_NativeEntryResolver value) const {
StoreNonPointer<Dart_NativeEntryResolver, Dart_NativeEntryResolver,
std::memory_order_relaxed>(&untag()->native_entry_resolver_,
value);
}
void set_native_entry_symbol_resolver(
Dart_NativeEntrySymbol native_symbol_resolver) const {
StoreNonPointer<Dart_NativeEntrySymbol, Dart_NativeEntrySymbol,
std::memory_order_relaxed>(
&untag()->native_entry_symbol_resolver_, native_symbol_resolver);
}
template <typename FieldType, typename ValueType, std::memory_order order>
void StoreNonPointer(const FieldType* addr, ValueType value) const {
// Can't use Contains, as it uses tags_, which is set through this method.
ASSERT(reinterpret_cast<uword>(addr) >= UntaggedObject::ToAddr(ptr()));
reinterpret_cast<std::atomic<FieldType>*>(const_cast<FieldType*>(addr))
->store(value, order);
}
这里并没有用到memcpy或者memmove之类的函数,因此回到我们最开始那个问题,dart调用C 是直接内存赋值的方法,这就解释了为什么dart到C这一层调用如此之快,因为dart和C方法的指针都指向了同一个内存区域,不需要拷贝和序列化,这一点上Flutter确实比RN要性能更好。
参考链接: