前言
客户端开发中,跨平台和动态性已是老生常谈的话题了,也诞生了ReactNative、Weex、Flutter等大前端方向的技术。
Kraken作为一款上层基于W3C标准实现,底层基于Flutter渲染的高性能渲染引擎,同时兼顾了跨平台和动态化的特性。对业务的快速迭代起到了很关键的作用。
其中事件的注册与分发在Flutter和JS的交互中算是其中比较典型的场景,今天就事件通道的原理跟大家分享一下学习Kraken源码的一些收获。
方案简介
首先简单介绍一下该方案的结构,对事件通道的场景有个概念。
如下图在Flutter页面中内嵌了很多JS卡片组件,这些组件的布局结构由js代码提供,服务端下发,通过渲染引擎将js组件翻译成widget组件,插入widget树中交由Flutter渲染。
本文要介绍的就是该场景下用户手指从按下滑动到抬起过程中事件是如何从Flutter侧传递到js侧并由js消费的
事件通道
架构图
kraken事件通道整体分为三层架构(JS业务层、Flutter容器层、C++引擎层)、两条链路(注册、分发)
流程概述
如下图绿色为注册流程,红色为分发流程:
注册:
1. C++侧将eventType和callback进行绑定,分发时通过eventType回调callback
2. 通过FFI方式给Flutter侧发送事件注册指令
3. Flutter侧根据id找到对应的Element,对eventType、Element、RenderObject等进行绑定,为分发做准备
分发:
1. Pointer事件在RenderObject树中正常流转到Kraken根节点
2. 根据注册流程中提前绑定好的eventType、Element、RenderObject关系,找出当前路径上的Element并梳理出已注册的事件类型,遍历路径上的Element
3. Flutter和C++侧都维护了一个一一对应的Element树,每对Element拥有唯一id,通过id找到对应C++侧的Element
4. 找到Element在注册时eventType绑定的callback,进行回调
经过上述的流程介绍,相信大家在脑中对事件通道已经有了一个整体的认知,下面将从源码角度进行分析:大家可以从Kraken官网下载源码一步步跟着理解
注册
JS侧
基于W3C标准,JS侧注册事件的代码和前端开发一样,下面是js中事件注册监听的示例代码:
btn.addEventListener(eventType, callback)
C++侧
解析到JS代码中的addEventListener语句时,C++侧做了下面两件事:
1. 发送名为addEvent的UICommand指令给flutter侧,指令内容主要有:targetId(Element节点唯一id,Flutter侧可通过该targetId找到对应的Element组件)、指令名称、eventType。
2. 将eventType作为key,callbackList作为value维护到集合m_eventListenerMap中,后续事件分发可通过eventType找到所有callbackList进行回调
JSValue EventTarget::addEventListener(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
// ...
eventTargetInstance->m_context->uiCommandBuffer()->addCommand(eventTargetInstance->m_eventTargetId, UICommand::addEvent, args_01, nullptr);
eventTargetInstance->m_eventListenerMap.add(eventType, JS_DupValue(ctx, callback));
// ...
}
flutter侧
1. 在事件注册之前有类似createElement的方法创建Element,在Flutter侧会维护一个Element集合,key为targetId,值为Element节点:(其中Element继承自Node,Node又继承自EventTarget,这里的设计跟Flutter中RenderObject继承自HitTestTarget一样)
//维护Element节点和targetId的集合
Map<int, EventTarget> _eventTar