Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

插件化方案 #5

Closed
wssgcg1213 opened this issue Feb 2, 2021 · 2 comments
Closed

插件化方案 #5

wssgcg1213 opened this issue Feb 2, 2021 · 2 comments
Assignees
Labels

Comments

@wssgcg1213
Copy link
Member

No description provided.

@andycall
Copy link
Member

andycall commented Feb 4, 2021

Kraken 插件化设计

有关 Kraken 能力边界划分:

核心能力

  • Kraken_bridge
  • CSS 能力全集
  • Element
    • div
    • span
    • strong
    • a
    • canvas
    • img
    • input
    • p
    • object
  • 手势能力
  • API
    • fetch
    • method_channel
    • performance_timing
    • setTimeout, setInterval
    • requestAnimationFrame

Kraken 插件化拆分的设计的目标:

  1. 进一步降低用户包大小的增量,避免一些不必要的功能被打包进 App
  2. 插件化设计是设计一套通用的扩展能力 API,然后一些可以拆分的能力通过这些 API 进行能力扩展。这个 API 既需要满足当前拆分的设计,也需要满足未来 Kraken 开源之后,在社区被建立 Kraken 周边插件生态的基础设施。

kraken 插件的加载方式

Kraken 的插件就是一个 Flutter plugin,采用和其他 flutter plugin 一样的加载方式。可以通过 pub 进行安装

Kraken 扩展能力

在设计上, Kraken 将支持 2 种能力的扩展:

  1. 渲染能力扩展
  2. API 能力扩展


渲染能力扩展指用户可以通过编写 flutter plugin 来为 Kraken 添加更多的渲染能力支持。用户在这里可以使用自定义的 RenderObject,并且会给用户提供一整套的生命周期 API 去管理和驱动用户自定义的 RenderObject。
渲染能力的目标是支持例如视频播放器,PlatformView 渲染,WebView 渲染,Camera 等能力的支持。


API 能力扩展指用户可以通过编写 flutter plugin 来为 Kraken 添加更多的 JS API。Kraken 将给插件开发者提供稳定可靠的 API 帮助他们实现 JS API 和 Dart 层之间的通信。

渲染能力扩展设计

Kraken 的渲染能力所承载的实现为 Element 对象。Element 对象在 JS 环境 (C++ 实现)和 Dart 环境中各有一份对象的实现。它们之间会通过一个 NativeEventTarget 的结构体来保持相互的引用。通过这样的一个结构体,JS 环境可以直接调用 Dart 环境中的函数,同时 Dart 环境也可以直接调用 JS 环境(C++实现)中实现的函数。

Dart 环境中的 Element 扩展

import 'package:kraken/dom.dart';
import 'package:kraken/rendering.dart';
import 'dart:ffi';
import 'package:ffi/ffi.dart';

class NativeCustomElement extends Struct {
  Pointer<NativeElement> nativeElement;
}

const String CUSTOM = 'CUSTOM';

class CustomElement extends Element {
  CustomElement(int targetId, Pointer<CustomElement> nativePtr, ElementManager elementManager)
      : super(
          targetId,
          nativePtr.ref.nativeElement,
          elementManager,
          CUSTOM
        );
  
  // Fired before this element's renderObject attach to renderObject tree.
  @override
  void willAttachRenderer() {
    super.willAttachRenderer();
  }
  
  // Fired when this element's renderObject attach to renderObject tree.
  @override
  void attachTo(Element parent, {RenderObject after}) {
   	super.attachTo(parent, after: after);
  }

  // Fired after this element's renderObject attach to renderObject 
  @override
  void didDetachRenderer() {
    super.didDetachRenderer();
  }

  // Fired when this element is disposed.
  @override
  void dispose() {
    super.dispose();
  }
  
  // Fired when this element's renderObject detached from parent
  @override
  void detach() {
    super.detach();
  }

  // Fired when setting string property of the element in JS environment.
  @override
  void setProperty(String key, value) {
    super.setProperty(key, value);
  }

  // Fired when reading string property of the element in JS environment.
  @override
  dynamic getProperty(String key) {
    return super.getProperty(key);
  }

  // Fired when remove string property of the element in JS environment.
  @override
  void removeProperty(String key) {
    super.removeProperty(key);
  }
 
  // Fired when add child node of this element.
  @override
  Node appendChild(Node child) {
  	super.appendChild(child);
  }
  
  // Fired when remove child node of this element.
  Node removeChild(Node child) {
		super.removeChild(child);
  }
  
  // Fired when insert child node before a referenceNode.
  Node insertBefore(Node child, Node referenceNode) {
  	super.insertBefore(child, referenceNode);
  }
  
  // Fired when element.addEventListener() js api called.
  void addEvent(String eventType) {
    super.addEvent(eventType);
  }
}


然后再通过 Element.defineElement API 就可以注册到 Kraken 中。

defineElement(CUSTOM, (id, nativePtr, elementManager) => CustomElement(id, nativePtr.cast<NativeCustomElement>(), elementManager));

JS 环境中的 Element 扩展

Kraken 中的 JS Element 采用 C++ 进行实现,因此 JS 环境中的 Element 扩展也需要采用 C++ 进行开发。通过 Kraken 所提供的 Headers 就可以编写 Kraken Element 扩展。

using GetContext = NativeCanvasRenderingContext2D *(*)(NativeCanvasElement *nativeCanvasElement,
                                                       NativeString *contextId);

struct NativeCanvasElement {
  NativeCanvasElement() = delete;
  NativeCanvasElement(NativeElement *nativeElement) : nativeElement(nativeElement){};

  NativeElement *nativeElement;

  GetContext getContext{nullptr};
};

class JSCanvasElement : public JSElement {
public:
  static std::unordered_map<JSContext *, JSCanvasElement *> instanceMap;
  OBJECT_INSTANCE(JSCanvasElement)

  JSObjectRef instanceConstructor(JSContextRef ctx, JSObjectRef constructor, size_t argumentCount,
                                  const JSValueRef *arguments, JSValueRef *exception) override;

private:
  JSCanvasElement() = delete;
  ~JSCanvasElement();
  explicit JSCanvasElement(JSContext *context);
};

class CanvasElementInstance : public ElementInstance {
public:
  static JSValueRef getContext(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount,
                               const JSValueRef arguments[], JSValueRef *exception);

  CanvasElementInstance() = delete;
  explicit CanvasElementInstance(JSCanvasElement *jsCanvasElement);
  ~CanvasElementInstance();

  // called when reading properties on canvas element	
  JSValueRef getProperty(std::string &name, JSValueRef *exception) override;
  // called when setting properties on canvas element
  void setProperty(std::string &name, JSValueRef value, JSValueRef *exception) override;
  // called when read property names (Object.keys) on canvas element.
  void getPropertyNames(JSPropertyNameAccumulatorRef accumulator) override;

 
  NativeCanvasElement *nativeCanvasElement;

private:
  JSFunctionHolder m_getContext{context, this, "getContext", getContext};
};


通过调用 Element.defineElement 就可以注册到 Kraken 中

JSElement::defineElement("canvas", [](JSContext *context) -> ElementInstance* {
	return new CanvasElementInstance(JSCanvasElement::instance(context));
});

API 能力扩展设计

API 能力的扩展分为两个部分:Module 的扩展和 JS API 扩展

Module 扩展


在 Kraken 中,提供单独 API 相关的能力都称之为 Module。通过在 Dart 中实现一个 Module 来完成 Kraken 一个 API 所需要的功能,例如提供 WebSocket 能力的 WebSocketModule 等。

class CustomModule extends BaseModule {
  CustomModule(ModuleManager moduleManager) : super(moduleManager);

  // Invoke when Kraken page disposed.
  @override
  void dispose() {
    // TODO: implement dispose
  }

  // Invoke when JS api kraken.invokeModule calls.
  @override
  String invoke(List<dynamic> params, InvokeModuleCallback callback) {
    
  }
}

然后再通过下面的方法将 Module 注册到 Kraken 中。

ModuleManager.defineNewModule('DemoModule', CustomModule(controller.module.moduleManager));

JS API 扩展

Module 的 JS API 扩展推荐使用 JS 进行实现。Kraken 提供了以下几个 API 用于调用 Dart 层实现的 Module。

  • kraken.invokeModule: JS 层调用这个 API 将会调用转发到 Dart 中 Module 的 invoke 函数中
  • kraken.addKrakenModuleListener 注册一个监听,用于监听 Dart 层触发的 Module 事件

触发自定义 Module 事件

Kraken 提供了 emitModuleEvent 方法用于向 JS 环境中的 Module 触发自定义事件。

CloseEvent event = CloseEvent(0, '', true);
moduleManager.emitModuleEvent('WebSocket', event);


通过调用 kraken.addKrakenModuleListener 注册一个回调,就可以接收到 emitModuleEvent 所触发到事件了。

kraken.addKrakenModuleListener((method, event) => {
  if (method == "WebSocket") {
	 	// dispatch websocket event.
  }
});

Q & A

如何实现 Element 上方法的调用


Kraken 通过使用 Dart FFI 来实现 C++ 与 Dart 之间的通信,通常 Element 之间的方法调用都是通过在 C++ 与 Dart 之间的共享方法都是挂载在对应的 NativeXXX 结构体上。例如在 C++ 中的 CanvasElementInstance 和 Dart 中的 CanvasElement,它们之间的共享结构体是 NativeCanvasElement。


首先需要 Dart 层准备一个静态函数,然后通过 Pointer.fromFunction 方法转化一个 C 函数,然后挂载到 NativeCanvasElement 这个结构体上,当 Dart 将函数当指针地址写入到结构之后, C++ 就可以立即直接通过函数指针进行访问,无需再做任何其他操作。

class CanvasElement extends Element {
  static SplayTreeMap<int, Element> _nativeMap = SplayTreeMap();

  static CanvasElement getCanvasElementOfNativePtr(Pointer<NativeCanvasElement> nativeCanvasElement) {
    CanvasElement canvasElement = _nativeMap[nativeCanvasElement.address];
    assert(canvasElement != null, 'Can not get canvasElement from nativeElement: $nativeCanvasElement');
    return canvasElement;
  }

  static Pointer<NativeCanvasRenderingContext2D> _getContext(
      Pointer<NativeCanvasElement> nativeCanvasElement, Pointer<NativeString> contextId) {
    CanvasElement canvasElement = getCanvasElementOfNativePtr(nativeCanvasElement);
    canvasElement.getContext(nativeStringToString(contextId));
  }

  final Pointer<NativeCanvasElement> nativeCanvasElement;

  CanvasElement(int targetId, this.nativeCanvasElement, ElementManager elementManager)
      : super(
          targetId,
          nativeCanvasElement.ref.nativeElement,
          elementManager,
          defaultStyle: _defaultStyle,
          isIntrinsicBox: true,
          repaintSelf: true,
          tagName: CANVAS,
        ) {
    nativeCanvasElement.ref.getContext = nativeGetContext;

    // Keep reference so that we can search back with nativePtr from bridge.
    _nativeMap[nativeCanvasElement.address] = this;
  }
  // RenderingContext? getContext(DOMString contextId, optional any options = null);
  CanvasRenderingContext getContext(String contextId, {dynamic options}) {
   	// ...
  }
}
 NativeCanvasRenderingContext2D *nativeCanvasRenderingContext2D =
    elementInstance->nativeCanvasElement->getContext(elementInstance->nativeCanvasElement, &contextId);

NativeElement的介绍

image

如何自定义事件

在 W3C 标准中,定义了各种不同类型的事件。每个事件之间的差异在于不同的事件提供不一样的参数和数据。
因此对于一些特定的触发行为,就需要使用特定的事件来完成。


Kraken 中提供了基础的 Event 对象实现,包括 Dart 环境和 JS 环境。


在 Dart 环境中扩展事件

class CloseEvent extends Event {
  /// An unsigned short containing the close code sent by the server
  final int code;

  /// Indicating the reason the server closed the connection.
  final String reason;

  /// Indicates whether or not the connection was cleanly closed
  final bool wasClean;

  CloseEvent(this.code, this.reason, this.wasClean) :
        assert(reason != null),
        super(EVENT_CLOSE);

  Pointer<NativeCloseEvent> toNative() {
    Pointer<NativeCloseEvent> closeEvent = allocate<NativeCloseEvent>();
    Pointer<NativeEvent> nativeEvent = super.toNative().cast<NativeEvent>();
    closeEvent.ref.nativeEvent = nativeEvent;
    closeEvent.ref.code = code;
    closeEvent.ref.reason = stringToNativeString(reason);
    closeEvent.ref.wasClean = wasClean ? 1 : 0;
    return closeEvent;
  }
}

在 JS 环境中扩展事件


class JSCloseEvent : public JSEvent {
public:
  DEFINE_OBJECT_PROPERTY(CloseEvent, 3, code, reason, wasClean)

  static std::unordered_map<JSContext *, JSCloseEvent *> instanceMap;
  OBJECT_INSTANCE(JSCloseEvent)

  JSObjectRef instanceConstructor(JSContextRef ctx, JSObjectRef constructor, size_t argumentCount,
                                  const JSValueRef *arguments, JSValueRef *exception) override;

  JSValueRef getProperty(std::string &name, JSValueRef *exception) override;

protected:
  JSCloseEvent() = delete;
  ~JSCloseEvent();
  explicit JSCloseEvent(JSContext *context);
};


class CloseEventInstance : public EventInstance {
public:
  CloseEventInstance() = delete;
  explicit CloseEventInstance(JSCloseEvent *jsCloseEvent, NativeCloseEvent *nativeCloseEvent);
  explicit CloseEventInstance(JSCloseEvent *jsCloseEvent, JSStringRef data, JSValueRef closeEventInit, JSValueRef *exception);
  JSValueRef getProperty(std::string &name, JSValueRef *exception) override;
  void setProperty(std::string &name, JSValueRef value, JSValueRef *exception) override;
  void getPropertyNames(JSPropertyNameAccumulatorRef accumulator) override;
  ~CloseEventInstance() override;

  NativeCloseEvent *nativeCloseEvent;

private:
  double code;
  bool wasClean;
  JSStringHolder m_reason{context, ""};
};

struct NativeCloseEvent {
  NativeCloseEvent() = delete;
  explicit NativeCloseEvent(NativeEvent *nativeEvent) : nativeEvent(nativeEvent){};

  NativeEvent *nativeEvent;
  int64_t code;
  NativeString *reason;
  int64_t wasClean;
};

@cnryb
Copy link
Member

cnryb commented Aug 20, 2021

@andycall 写这么好,不放文档里可惜了

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants