美文网首页
从dart如何调用skia绘图说起

从dart如何调用skia绘图说起

作者: FingerStyle | 来源:发表于2022-04-19 15:51 被阅读0次

    前段时间面试某大厂,面试官问了一个问题,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等方法的签名和实现,重点来了!这里面还有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

    相关文章

      网友评论

          本文标题:从dart如何调用skia绘图说起

          本文链接:https://www.haomeiwen.com/subject/wlidertx.html