从dart如何调用skia绘图说起

2022-04-19  本文已影响0人  FingerStyle

前段时间面试某大厂,面试官问了一个问题,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调用原生消息主要有三种方式:

借用一张别人的图


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等方法的签名和实现,重点来了!这里面还有scheduleFramerespondToPlatformMessage等方法,这些都是很涉及到页面刷新(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要性能更好。

参考链接:

  1. https://juejin.cn/post/6844903661248708615
  2. https://zhuanlan.zhihu.com/p/81023017
  3. http://events.jianshu.io/p/a384a796b94f
上一篇下一篇

猜你喜欢

热点阅读