赞
踩
最近研究混合栈渲染时,遇到了一个混合栈下的事件分发问题,为此看了一下flutter接入原生view后,其中的事件分发流程。为了方便后期查阅,做此记录,也希望能帮到有需要的人。
文中所讨论的原生view为: AndroidViewSurface
,即:hybird composition
,其次还将涉及到Flutter的启动流程
,如果对这两者不熟悉的话,建议浏览这下面的文章:
Flutter在Android平台上启动时,Native层做了什么?
Flutter——Hybrid Composition混合图层的原理分析
由启动流程可知,最先创建的 FlutterView
类似于容器,其持有flutter绘制所需的surface
,那么我们就由它的onTouchEvent
方法看起。
其实,根据Flutter框架的定位,也可以猜到事件分发是由平台负责的。
@Override
public boolean onTouchEvent(@NonNull MotionEvent event) {
...无关代码...
return androidTouchProcessor.onTouchEvent(event);
}
这里调用了 androidTouchProcessor.onTouchEvent(event)
。
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
进行事件的传递,我们继续看。
这里相对比较简单,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); }
平台层的事件分发还是比较简单明了的,我们跟着事件继续往下走。
通过开头的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_++; }
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侧了。
如果你对WidgetsFlutterBinding及其混入类不熟悉,可以取查阅flutter的相关文章,有很多。
我们单刀直入GestureBinding
类,可以看到初始化方法:
@override
void initInstances() {
super.initInstances();
_instance = this;
window.onPointerDataPacket = _handlePointerDataPacket;
}
我们先看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
此方法会绑定到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
是如何处理事件的。
关于混合图层的创建即实现原理请参考这篇文章: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做了优化。
至此,要梳理的流程业已完毕,谢谢大家的阅读,如有错误欢迎指出。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。