Flutter之Hybird-composition View的事件分发流程
Posted 静水流深zz
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter之Hybird-composition View的事件分发流程相关的知识,希望对你有一定的参考价值。
前言
最近研究混合栈渲染时,遇到了一个混合栈下的事件分发问题,为此看了一下flutter接入原生view后,其中的事件分发流程。为了方便后期查阅,做此记录,也希望能帮到有需要的人。
文中所讨论的原生view为: androidViewSurface
,即:hybird composition
,其次还将涉及到Flutter的启动流程
,如果对这两者不熟悉的话,建议浏览这下面的文章:
Flutter在Android平台上启动时,Native层做了什么?
Flutter——Hybrid Composition混合图层的原理分析
平台层: Android
由启动流程可知,最先创建的 FlutterView
类似于容器,其持有flutter绘制所需的surface
,那么我们就由它的onTouchEvent
方法看起。
其实,根据Flutter框架的定位,也可以猜到事件分发是由平台负责的。
Flutter:onTouchEvent
@Override
public boolean onTouchEvent(@NonNull MotionEvent event)
...无关代码...
return androidTouchProcessor.onTouchEvent(event);
这里调用了 androidTouchProcessor.onTouchEvent(event)
。
AndroidTouchProcessor: onTouchEvent
public boolean onTouchEvent(@NonNull MotionEvent event)
return onTouchEvent(event, IDENTITY_TRANSFORM);
此方法会进一步调用同名函数:
public boolean onTouchEvent(@NonNull MotionEvent event, Matrix transformMatrix)
int pointerCount = event.getPointerCount();
// Prepare a data packet of the appropriate size and order.
// 对event进行转换和存储,并传递给flutter侧
ByteBuffer packet =
ByteBuffer.allocateDirect(pointerCount * POINTER_DATA_FIELD_COUNT * BYTES_PER_FIELD);
packet.order(ByteOrder.LITTLE_ENDIAN);
//...省略不少代码...
//主要是根据event类型,并通过addPointerForIndex(...)方法对packet进行数据填充
// 将packet发送到flutter
renderer.dispatchPointerDataPacket(packet, packet.position());
return true;
最终调用 FlutterRenderer.dispatchPointerDataPacket
进行事件的传递,我们继续看。
dispatchPointerDataPacket
这里相对比较简单,renderer的 dispatchPointerDataPacket
方法直接调用了FlutterJni
的dispatchPointerDataPacket
方法:
//FlutterRenderer
public void dispatchPointerDataPacket(@NonNull ByteBuffer buffer, int position)
flutterJNI.dispatchPointerDataPacket(buffer, position);
//FlutterJNI
/** 这里会将pointer packet 转到对应的engine层的方法:nativeDispatchPointerDataPacket*/
@UiThread
public void dispatchPointerDataPacket(@NonNull ByteBuffer buffer, int position)
ensureRunningOnMainThread();
ensureAttachedToNative();
//调用engine层的方法
nativeDispatchPointerDataPacket(nativeShellHolderId, buffer, position);
平台层的事件分发还是比较简单明了的,我们跟着事件继续往下走。
中间层: Engine
Platform Thread
通过开头的Flutter在Android平台上启动时,Native层做了什么?, 我们知道应用开始会注册一些engine的方法,具体实现在platform_view_android_jni_impl.cc
:
bool RegisterApi(JNIEnv* env)
static const JNINativeMethod flutter_jni_methods[] =
//...省略部分注册的方法
.name = "nativeDispatchPointerDataPacket", //android 侧的方法名
.signature = "(JLjava/nio/ByteBuffer;I)V",//android 方法名的签名(参数类型)
.fnPtr = reinterpret_cast<void*>(&DispatchPointerDataPacket),//对应engine层的方法指针
,
//...省略部分注册的方法
当我们在android侧调用nativeDispatchPointerDataPacket
方法时,会调用engine的DispatchPointerDataPacket
方法,其实现如下:
static void DispatchPointerDataPacket(JNIEnv* env,
jobject jcaller,
jlong shell_holder,
jobject buffer,
jint position)
//通过GetDirectBufferAddress 获取到指向buffer的指针
uint8_t* data = static_cast<uint8_t*>(env->GetDirectBufferAddress(buffer));
//在通过position 最终生成一个指向packet的指针
auto packet = std::make_unique<flutter::PointerDataPacket>(data, position);
//然后将packet传递到 platform view的DispatchPointerDataPacket方法中
ANDROID_SHELL_HOLDER->GetPlatformView()->DispatchPointerDataPacket(
std::move(packet));
android端的packet通过上面的方法,传递到了platform view
的DispatchPointerDataPacket
方法。
//这里 holder 返回了一个PlatformView的子类PlatformViewAndroid
fml::WeakPtr<PlatformViewAndroid> AndroidShellHolder::GetPlatformView()
FML_DCHECK(platform_view_);
return platform_view_;
虽然返回的是 PlatformViewAndroid
,但是父类PlatformViewAndroid
的 DispatchPointerDataPacket
方法并没有要求子类重写,且PlatformViewAndroid
内也确实没有重写这个方法,那么我们直接看其父类的实现:
void PlatformView::DispatchPointerDataPacket(
std::unique_ptr<PointerDataPacket> packet)
delegate_.OnPlatformViewDispatchPointerDataPacket(
pointer_data_packet_converter_.Convert(std::move(packet)));
此处的delegate_
为PlatformView
内部的一个抽象类,在AttachJNI
方法初始化AndroidShellHolder
中,其内部对AndroidPlatformView
初始化时而实例化的,这里简单介绍一下:
AttachJNI / AndroidShellHolder等 可以参见上面的文章
AndroidShellHolder::AndroidShellHolder(
flutter::Settings settings,
std::shared_ptr<PlatformViewAndroidJNI> jni_facade,
bool is_background_view)
: settings_(std::move(settings)), jni_facade_(jni_facade)
...省略部分代码
//初始化shell时,传入此回调
Shell::CreateCallback<PlatformView> on_create_platform_view =
[is_background_view, &jni_facade, &weak_platform_view](Shell& shell)
std::unique_ptr<PlatformViewAndroid> platform_view_android;
//这里通过回调,我们将shell作为delegate传入PlatformViewAndroid
platform_view_android = std::make_unique<PlatformViewAndroid>(
shell, // delegate
shell.GetTaskRunners(), // task runners
jni_facade, // JNI interop
shell.GetSettings()
.enable_software_rendering, // use software rendering
!is_background_view // create onscreen surface
);
weak_platform_view = platform_view_android->GetWeakPtr();
auto display = Display(jni_facade->GetDisplayRefreshRate());
shell.OnDisplayUpdates(DisplayUpdateType::kStartup, display);
return platform_view_android;
;
...省略部分代码
ok,通过上面这一段代码,我们知道delegate
实例是怎么来的了,接下来回到正题,继续看PlatformView::DispatchPointerDataPacket
:
void PlatformView::DispatchPointerDataPacket(
std::unique_ptr<PointerDataPacket> packet)
delegate_.OnPlatformViewDispatchPointerDataPacket(
pointer_data_packet_converter_.Convert(std::move(packet)));
其又调用了Shell
的OnPlatformViewDispatchPointerDataPacket
方法:
void Shell::OnPlatformViewDispatchPointerDataPacket(
std::unique_ptr<PointerDataPacket> packet)
...省略部分代码
//这里做了个线程切换,向UI Thread 添加了一个任务,
//并最终执行engine的DispatchPointerDataPacket方法
//注: 这里的UI thread就是flutter 代码执行的线程
task_runners_.GetUITaskRunner()->PostTask(
//shell为engine的子类,并持有了engine的一个弱引用weak_engine_
fml::MakeCopyable([engine = weak_engine_, packet = std::move(packet),
flow_id = next_pointer_flow_id_]() mutable
if (engine)
engine->DispatchPointerDataPacket(std::move(packet), flow_id);
));
next_pointer_flow_id_++;
UI Thread
engine->DispatchPointerDataPacket
的这个方法有点绕,其实现如下:
void Engine::DispatchPointerDataPacket(
std::unique_ptr<PointerDataPacket> packet,
uint64_t trace_flow_id)
...
pointer_data_dispatcher_->DispatchPacket(std::move(packet), trace_flow_id);
看起来它又调用了dispatcher
,但最终还是调用的engine实现的接口方法DoDispatchPacket
:
void Engine::DoDispatchPacket(std::unique_ptr<PointerDataPacket> packet,
uint64_t trace_flow_id)
animator_->EnqueueTraceFlowId(trace_flow_id);
if (runtime_controller_)
runtime_controller_->DispatchPointerDataPacket(*packet);
进入RuntimeController::DispatchPointerDataPacket
bool RuntimeController::DispatchPointerDataPacket(
const PointerDataPacket& packet)
if (auto* platform_configuration = GetPlatformConfigurationIfAvailable())
...other code
//这里获取到了id为0的window并调用了DispatchPointerDataPacket
platform_configuration->get_window(0)->DispatchPointerDataPacket(packet);
return true;
return false;
这个window
,如果你看过关于flutter源码介绍之类的文章(如:渲染流程),应该是不会陌生的,这个window
就是与flutter端的window
相对应的。
void Window::DispatchPointerDataPacket(const PointerDataPacket& packet)
std::shared_ptr<tonic::DartState> dart_state = library_.dart_state().lock();
if (!dart_state)
return;
tonic::DartState::Scope scope(dart_state);
//这里对咱们从android端挪过来的packet 做了个转换
const std::vector<uint8_t>& buffer = packet.data();
Dart_Handle data_handle =
tonic::DartByteData::Create(buffer.data(), buffer.size());
if (Dart_IsError(data_handle))
return;
//类似jni的调用,拉起了flutter端的 "_dispatchPointerDataPacket"方法
tonic::LogIfError(tonic::DartInvokeField(
library_.value(), "_dispatchPointerDataPacket", data_handle));
接下来就要转到flutter侧了。
Flutter层
如果你对WidgetsFlutterBinding及其混入类不熟悉,可以取查阅flutter的相关文章,有很多。
我们单刀直入GestureBinding
类,可以看到初始化方法:
@override
void initInstances()
super.initInstances();
_instance = this;
window.onPointerDataPacket = _handlePointerDataPacket;
window.onPointerDataPacket
我们先看window.onPointerDataPacke
set onPointerDataPacket(PointerDataPacketCallback? callback)
platformDispatcher.onPointerDataPacket = callback;
platformDispatcher
的方法:
PointerDataPacketCallback? get onPointerDataPacket => _onPointerDataPacket;
PointerDataPacketCallback? _onPointerDataPacket;
Zone _onPointerDataPacketZone = Zone.root;
set onPointerDataPacket(PointerDataPacketCallback? callback)
_onPointerDataPacket = callback;
_onPointerDataPacketZone = Zone.current;
最终会将_handlePointerDataPacket
方法赋值给platformDispatcher
的_onPointerDataPacket
,而_onPointerDataPacket
则会在下面这个方法中调用:
// Called from the engine, via hooks.dart
void _dispatchPointerDataPacket(ByteData packet)
if (onPointerDataPacket != null)
_invoke1<PointerDataPacket>(
onPointerDataPacket,
_onPointerDataPacketZone,
_unpackPointerDataPacket(packet),
);
从上面的注释可以看到,这里的方法最终与咱们在engine
层中的调用链关联了起来,即被下面这个方法所调用的:
tonic::LogIfError(tonic::DartInvokeField(
library_.value(), "_dispatchPointerDataPacket", data_handle));
当然,关联节点是在hooks.dart
文件中做的。
@pragma('vm:entry-point')
// ignore: unused_element
void _dispatchPointerDataPacket(ByteData packet)
PlatformDispatcher.instance._dispatchPointerDataPacket(packet);
接下来看_handlePointerDataPacket
_handlePointerDataPacket
此方法会绑定到window
并用于响应engine
的回调,
void _handlePointerDataPacket(ui.PointerDataPacket packet)
// We convert pointer data to logical pixels so that e.g. the touch slop can be
// defined in a device-independent manner.
_pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio));
if (!locked)
_flushPointerEventQueue();
本文旨在分析原生view的事件分发流程,为了避免跑题和压缩篇幅,flutter侧事件分发将会简要概括。
而其内部经过相对简单的调用链最终会进入下面这个方法:
void _handlePointerEventImmediately(PointerEvent event)
// 第一步
HitTestResult? hitTestResult;
if (event is PointerDownEvent || event is PointerSignalEvent || event is PointerHoverEvent)
assert(!_hitTests.containsKey(event.pointer));
hitTestResult = HitTestResult();
hitTest(hitTestResult, event.position);
if (event is PointerDownEvent)
_hitTests[event.pointer] = hitTestResult;
assert(()
if (debugPrintHitTestResults)
debugPrint('$event: $hitTestResult');
return true;
());
else if (event is PointerUpEvent || event is PointerCancelEvent)
hitTestResult = _hitTests.remove(event.pointer);
else if (event.down)
// Because events that occur with the pointer down (like
// [PointerMoveEvent]s) should be dispatched to the same place that their
// initial PointerDownEvent was, we want to re-use the path we found when
// the pointer went down, rather than do hit detection each time we get
// such an event.
hitTestResult = _hitTests[event.pointer];
assert(()
if (debugPrintMouseHoverEvents && event is PointerHoverEvent)
debugPrint('$event');
return true;
());
if (hitTestResult != null ||
event is PointerAddedEvent ||
event is PointerRemovedEvent)
assert(event.position != null);
// 第二步
dispatchEvent(event, hitTestResult);
这个方法可以大致分为两步,在此做一个简要概括:
第一步:
创建一个root hitTestResult
,其内部拥有一个List
类型的_path
,随后调用rootView
的hitTest方法
并进而调用其child
的hitTest方法,依次向下一直遍历整个render
树。
这个过程中`root hitTestResult`会被一直传递
每当遍历到一个节点render
,便会根据自身的_size
是否包含pointer event positon
来确定是否加入到_path
中。
第二步:
调用dispatchEvent
方法,该方法会遍历_path
中的节点,并调用handleEvent方法
:
for (final HitTestEntry entry in hitTestResult.path)
try
//target 为renderObject,其实现了HitTestTarget接口
entry.target.handleEvent(event.transformed(entry.transform), entry);
catch (exception, stack)
...other code
经过上面这部分,我们大致了解了flutter的事件分发。现在回归正题,来看一下AndroidViewSurface
是如何处理事件的。
AndroidViewSurface
关于混合图层的创建即实现原理请参考这篇文章:Flutter——Hybrid Composition混合图层的原理分析。
AndroidViewSurface
继承自PlatformViewSurface
,它内部的一个重要方法是:
//创建一个render object,自定义过widget的朋友应该不陌生这个方法
@override
RenderObject createRenderObject(BuildContext context)
return PlatformViewRenderBox(controller: controller,
gestureRecognizers: gestureRecognizers, hitTestBehavior: hitTestBehavior);
我们继续往下走,PlatformViewRenderBox
:
//看到它的父类,再结合之前的内容,应该就是这里了
class PlatformViewRenderBox extends RenderBox with _PlatformViewGestureMixin
PlatformViewRenderBox(
required PlatformViewController controller,
required PlatformViewHitTestBehavior hitTestBehavior,
required Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,
) : assert(controller != null && controller.viewId != null && controller.viewId > -1),
assert(hitTestBehavior != null),
assert(gestureRecognizers != null),
//这个controller 也很重要
_controller = controller
this.hitTestBehavior = hitTestBehavior;
//这里更新了手势识别器
updateGestureRecognizers(gestureRecognizers);
...non-relative code
这里可以看到,dispatchPointerEvent
这个方法是从_controller(AndroidViewController)
中取到的。
void updateGestureRecognizers(Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers)
_updateGestureRecognizersWithCallBack(gestureRecognizers, _controller.dispatchPointerEvent);
我们来看一下_controller.dispatchPointerEvent
的实现:
@override
Future<void> dispatchPointerEvent(PointerEvent event) async
if (event is PointerHoverEvent)
return;
if (event is PointerDownEvent)
_motionEventConverter.handlePointerDownEvent(event);
_motionEventConverter.updatePointerPositions(event);
//将flutter 事件 转成 android 事件
final AndroidMotionEvent? androidEvent =
_motionEventConverter.toAndroidMotionEvent(event);
if (event is PointerUpEvent)
_motionEventConverter.handlePointerUpEvent(event);
else if (event is PointerCancelEvent)
_motionEventConverter.handlePointerCancelEvent(event);
if (androidEvent != null)
//发送转换后的事件
await sendMotionEvent(androidEvent);
跟着进入sendMotionEvent(androidEvent)
方法:
Future<void> sendMotionEvent(AndroidMotionEvent event) async
await SystemChannels.platform_views.invokeMethod<dynamic>(
'touch',
event._asList(viewId),
);
(○´・д・)ノ, 这里又将点击事件传回了Android端…后面就不再赘述了,这个事件最终会被对应的原生view进行消费。
结语
刚看到这里时我是比较纳闷的,觉得事件绕了很大一圈,不过转念一想Flutter作为上层主要消费区
,如果被跳过很可能造成事件错发的问题。
但是,我还是疑惑,混合图层下的事件消费
问题是否可以在engine层做个优化呢? 毕竟混合图层的渲染
就是在engine做了优化。
至此,要梳理的流程业已完毕,谢谢大家的阅读,如有错误欢迎指出。
其他Flutter相关文章
Flutter&Android 启动页(闪屏页)的加载流程和优化方案
以上是关于Flutter之Hybird-composition View的事件分发流程的主要内容,如果未能解决你的问题,请参考以下文章
Flutter 命令本质之 Flutter tools 机制源码深入分析
Flutter 命令本质之 Flutter tools 机制源码深入分析