Android11 事件分发流程

news/2024/9/23 10:03:28/

在Android 11 输入系统InputDispatcher和应用窗口建立联系一文中介绍到,当InputDispatcher写入数据后,客户端这边就会调用handleEvent方法接收数据

//frameworks\base\core\jni\android_view_InputEventReceiver.cpp
int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {//省略if (events & ALOOPER_EVENT_INPUT) {//之前构造数据的时候,events为ALOOPER_EVENT_INPUTJNIEnv* env = AndroidRuntime::getJNIEnv();status_t status = consumeEvents(env, false /*consumeBatches*/, -1, nullptr);mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");return status == OK || status == NO_MEMORY ? 1 : 0;}//省略

继续调用consumeEvents处理

//frameworks\base\core\jni\android_view_InputEventReceiver.cpp
status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {//省略for (;;) {uint32_t seq;InputEvent* inputEvent;status_t status = mInputConsumer.consume(&mInputEventFactory,consumeBatches, frameTime, &seq, &inputEvent);//1//省略case AINPUT_EVENT_TYPE_MOTION: {MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent);//使用inputEvent构造MotionEvent对象if ((motionEvent->getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {*outConsumedBatch = true;}inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);//创建java层的MotionEvent对象,并将该对象的mNativePtr指向c++的MotionEvent对象break;}//省略env->CallVoidMethod(receiverObj.get(),gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);//2//省略
}

注释1处接收InputDispatcher发过来的数据,并将数据封装成InputEvent对象,注释2处通过JNI调用InputEventReceiver的dispatchInputEvent方法
先来看一下如何接收数据的

//frameworks\native\libs\input\InputTransport.cpp
status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consumeBatches,nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {//省略while (!*outEvent) {if (mMsgDeferred) {// mMsg contains a valid input message from the previous call to consume// that has not yet been processed.mMsgDeferred = false;} else {// Receive a fresh message.status_t result = mChannel->receiveMessage(&mMsg);//1//省略switch (mMsg.header.type) {case InputMessage::Type::MOTION: {//省略MotionEvent* motionEvent = factory->createMotionEvent();if (!motionEvent) return NO_MEMORY;updateTouchState(mMsg);initializeMotionEvent(motionEvent, &mMsg);//2*outSeq = mMsg.body.motion.seq;*outEvent = motionEvent;//省略break;}}

注释1处接收数据,接收到的数据是InputMessage对象。注释2处根据读取到的InputMessage,创建motionEvent对象

//frameworks\native\libs\input\InputTransport.cpp
status_t InputChannel::receiveMessage(InputMessage* msg) {ssize_t nRead;do {nRead = ::recv(mFd.get(), msg, sizeof(InputMessage), MSG_DONTWAIT);//读fd} while (nRead == -1 && errno == EINTR);

consume方法得到数据并将数据封装成motionEvent对象后,回到consumeEvents方法,继续调用InputEventReceiver的dispatchInputEvent方法

//frameworks\base\core\java\android\view\InputEventReceiver.java
private void dispatchInputEvent(int seq, InputEvent event) {mSeqMap.put(event.getSequenceNumber(), seq);onInputEvent(event);
}

调用onInputEvent方法,WindowInputEventReceiver继承InputEventReceiver,调用WindowInputEventReceiver的onInputEvent方法

//frameworks\base\core\java\android\view\ViewRootImpl.java
final class WindowInputEventReceiver extends InputEventReceiver {public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {super(inputChannel, looper);}@Overridepublic void onInputEvent(InputEvent event) {Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventForCompatibility");//省略if (processedEvents != null) {//省略} else {enqueueInputEvent(event, this, 0, true);//注意第二个参数传入的是当前对象}}

enqueueInputEvent

//frameworks\base\core\java\android\view\ViewRootImpl.java
void enqueueInputEvent(InputEvent event,InputEventReceiver receiver, int flags, boolean processImmediately) {QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);//省略if (processImmediately) {//processImmediately传进来的是truedoProcessInputEvents();} else {scheduleProcessInputEvents();}}

doProcessInputEvents

//frameworks\base\core\java\android\view\ViewRootImpl.java
void doProcessInputEvents() {// Deliver all pending input events in the queue.while (mPendingInputEventHead != null) {//省略deliverInputEvent(q);}//省略}

deliverInputEvent

//frameworks\base\core\java\android\view\ViewRootImpl.java
private void deliverInputEvent(QueuedInputEvent q) {//省略try {//省略InputStage stage;if (q.shouldSendToSynthesizer()) {stage = mSyntheticInputStage;} else {stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;//1}//省略if (stage != null) {handleWindowFocusChanged();stage.deliver(q);//2} else {finishInputEvent(q);}} finally {Trace.traceEnd(Trace.TRACE_TAG_VIEW);}}

注释1处设置InputStage,对于触摸事件,默认是忽略输入法的,所以stage 为mFirstPostImeInputStage 对象。注释2处 调用mFirstPostImeInputStage 的deliver方法。
系统中有多个InputStage组成的一个链表,在setView方法中设置的

//frameworks\base\core\java\android\view\ViewRootImpl.java
// Set up the input pipeline.
CharSequence counterSuffix = attrs.getTitle();
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,"aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
InputStage imeStage = new ImeInputStage(earlyPostImeStage,"aq:ime:" + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,"aq:native-pre-ime:" + counterSuffix);

deliver方法的原理就是输入事件会经过这些InputStage依次处理(调用onProcess方法),如果事件已经被上一个消费处理了,后面的stage就不会处理了。触摸事件会传递到ViewPostImeInputStage中处理

//frameworks\base\core\java\android\view\ViewRootImpl.java
final class ViewPostImeInputStage extends InputStage {public ViewPostImeInputStage(InputStage next) {super(next);}@Overrideprotected int onProcess(QueuedInputEvent q) {if (q.mEvent instanceof KeyEvent) {return processKeyEvent(q);} else {final int source = q.mEvent.getSource();if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {return processPointerEvent(q);} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {return processTrackballEvent(q);} else {return processGenericMotionEvent(q);}}}

对于触摸事件,调用processPointerEvent继续处理

//frameworks\base\core\java\android\view\ViewRootImpl.java
private int processPointerEvent(QueuedInputEvent q) {final MotionEvent event = (MotionEvent)q.mEvent;mAttachInfo.mUnbufferedDispatchRequested = false;mAttachInfo.mHandlingPointerEvent = true;boolean handled = mView.dispatchPointerEvent(event);//1//省略return handled ? FINISH_HANDLED : FORWARD;
}

主要是调用mView的dispatchPointerEvent方法,这里的mView是DecorView,DecorView中没有实现该方法,在其父类View中实现

//frameworks\base\core\java\android\view\View.java
@UnsupportedAppUsagepublic final boolean dispatchPointerEvent(MotionEvent event) {if (event.isTouchEvent()) {return dispatchTouchEvent(event);} else {return dispatchGenericMotionEvent(event);}}

又回到DecorView的dispatchTouchEvent方法

//frameworks\base\core\java\com\android\internal\policy\DecorView.java@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {final Window.Callback cb = mWindow.getCallback();return cb != null && !mWindow.isDestroyed() && mFeatureId < 0? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);}

这里的callback就是Activity对象,调用Activity的dispatchTouchEvent方法。

//frameworks/base/core/java/android/app/Activity.javapublic boolean dispatchTouchEvent(MotionEvent ev) {if (ev.getAction() == MotionEvent.ACTION_DOWN) {onUserInteraction();}if (getWindow().superDispatchTouchEvent(ev)) {//1return true;}return onTouchEvent(ev);//2}

注释1处调用getWindow的superDispatchTouchEvent方法,getWindow返回的是一个PhoneWindow对象。注意返回值,如果返回ture的话,表明消费事件,注释2处Activity的onTouchEvent方法就不会执行。反之返回false的话使用onTouchEvent进行兜底,onTouchEvent如果是返回true,后面的InputStage就不会处理了,返回false则表明继续交给后面的InputStage处理

//frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) {return mDecor.superDispatchTouchEvent(event);}

又继续调用到DecorView的superDispatchTouchEvent方法

//frameworks/base/core/java/com/android/internal/policy/DecorView.java
public boolean superDispatchTouchEvent(MotionEvent event) {return super.dispatchTouchEvent(event);}

调用其父类ViewGroup的dispatchTouchEvent方法。在分析这个方法之前,先总结下事件是如何分发到ViewGroup的
在这里插入图片描述
事件是由DecorView分发给Activity,然后分发给window,最后又回到DecorView,再由DecorView分发给ViewGroup的。
ViewGroup接收到事件后,接下来就是将事件分发给具体的view了
ViewGroup事件分发

//frameworks/base/core/java/android/view/ViewGroup.java
@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {//省略boolean handled = false;//表明是否消费事件if (onFilterTouchEventForSecurity(ev)) {//是否符合安全策略final int action = ev.getAction();final int actionMasked = action & MotionEvent.ACTION_MASK;// Check for interception.final boolean intercepted;//是否拦截事件if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;//调用requestDisallowInterceptTouchEvent这个方法设置不允许拦截,if (!disallowIntercept) {intercepted = onInterceptTouchEvent(ev);//根据返回值判断是否允许拦截ev.setAction(action); // restore action in case it was changed} else {intercepted = false;//默认是不拦截}} else {// There are no touch targets and this action is not an initial down// so this view group continues to intercept touches.intercepted = true;//如果第一次的事件不是down的话,直接拦截}//省略// Check for cancelation.final boolean canceled = resetCancelNextUpFlag(this)|| actionMasked == MotionEvent.ACTION_CANCEL;// Update list of touch targets for pointer down, if needed.final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;TouchTarget newTouchTarget = null;boolean alreadyDispatchedToNewTouchTarget = false;//比较重要的参数if (!canceled && !intercepted) {//不拦截也不是取消事件的话进入//省略if (actionMasked == MotionEvent.ACTION_DOWN|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {//省略final int childrenCount = mChildrenCount;if (newTouchTarget == null && childrenCount != 0) {final float x = ev.getX(actionIndex);final float y = ev.getY(actionIndex);// Find a child that can receive the event.// Scan children from front to back.final ArrayList<View> preorderedList = buildTouchDispatchChildList();final boolean customOrder = preorderedList == null&& isChildrenDrawingOrderEnabled();final View[] children = mChildren;for (int i = childrenCount - 1; i >= 0; i--) {//遍历子iewfinal int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);//省略//如果子view不能接收事件或者触摸点不在该view上的话,忽略这个viewif (!canViewReceivePointerEvents(child)|| !isTransformedTouchPointInView(x, y, child, null)) {ev.setTargetAccessibilityFocus(false);continue;}newTouchTarget = getTouchTarget(child);//取出view的TouchTarget//忽略resetCancelNextUpFlag(child);if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {//开始分发并处理了//省略newTouchTarget = addTouchTarget(child, idBitsToAssign);//进入这里表示子view消费了事件,就会设置view的TouchTarget链表,mFirstTouchTarget就不为空alreadyDispatchedToNewTouchTarget = true;//设为truebreak;}}if (preorderedList != null) preorderedList.clear();}//省略}}// Dispatch to touch targets.if (mFirstTouchTarget == null) {// No touch targets so treat this as an ordinary view.handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);//注意第三个参数为null} else {// Dispatch to touch targets, excluding the new touch target if we already// dispatched to it.  Cancel touch targets if necessary.TouchTarget predecessor = null;TouchTarget target = mFirstTouchTarget;while (target != null) {final TouchTarget next = target.next;if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {//表明子view消费了事件handled = true;} else {final boolean cancelChild = resetCancelNextUpFlag(target.child)|| intercepted;//判断是否是取消事件if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) {handled = true;}//省略return handled;}

首先就是看看是不是需要拦截事件,判断是否通过requestDisallowInterceptTouchEvent这个方法,设置了ViewGroup不允许拦截,如果没有设置,再判断onInterceptTouchEvent的返回值,返回flase不表示不拦截。如果没有拦截则会遍历子view,依次使用dispatchTransformedTouchEvent处理,而如果拦截了话,也是通过dispatchTransformedTouchEvent处理,只不过传入的参数中,第3个参数为null
来看一下dispatchTransformedTouchEvent这个方法

//frameworks/base/core/java/android/view/ViewGroup.javaprivate boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) {final boolean handled;// Canceling motions is a special case.  We don't need to perform any transformations// or filtering.  The important part is the action, not the contents.final int oldAction = event.getAction();if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {event.setAction(MotionEvent.ACTION_CANCEL);//代表取消事件的话,将action设置为ACTION_CANCELif (child == null) {handled = super.dispatchTouchEvent(event);} else {handled = child.dispatchTouchEvent(event);}event.setAction(oldAction);//又设置回来return handled;}//省略// Perform any necessary transformations and dispatch.if (child == null) {handled = super.dispatchTouchEvent(transformedEvent);//如果第三个参数传入的是空,则调用自己父类的dispatchTouchEvent方法处理} else {final float offsetX = mScrollX - child.mLeft;final float offsetY = mScrollY - child.mTop;transformedEvent.offsetLocation(offsetX, offsetY);if (! child.hasIdentityMatrix()) {transformedEvent.transform(child.getInverseMatrix());}handled = child.dispatchTouchEvent(transformedEvent);//继续分发给子view处理}// Done.transformedEvent.recycle();return handled;}

dispatchTransformedTouchEvent的含义是如果child是ViewGroup的话,就继续调用ViewGroup的dispatchTouchEvent方法继续向下分发,如果child是view的话,则调用view的dispatchTouchEvent来处理事件。如果ViewGroup拦截了事件或者ViewGroup的孩子没有消费事件的话,也会调用View的dispatchTouchEvent来处理事件。来看一下view的dispatchTouchEvent方法

//frameworks/base/core/java/android/view/View.java
public boolean dispatchTouchEvent(MotionEvent event) {//省略if (onFilterTouchEventForSecurity(event)) {if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {result = true;}//noinspection SimplifiableIfStatementListenerInfo li = mListenerInfo;if (li != null && li.mOnTouchListener != null&& (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event)) {//设置过OnTouchListener优先调用result = true;}if (!result && onTouchEvent(event)) {//调用onTouchEvent方法result = true;}}//省略return result;}

对于事件的处理主要是判断view是不是设置过OnTouchListener,如果设置过,则调用其onTouch方法。如果OnTouch返回true的话,表示事件在这里被消费,后面的onTouchEvent就不会被调用。如果没有设置过OnTouchListener或者设置过,但是OnTouch返回false,则onTouchEvent会被调用。

上面的几个方法内容比较多,理解起来也比较费劲,用一张图总结下ViewGroup的事件分发流程

在这里插入图片描述
总结

  • 可以通过重写ViewGroup的onInterceptTouchEvent方法来实现对事件的拦截
  • 可以通过调用requestDisallowInterceptTouchEvent来禁止ViewGroup对事件拦截,这个优先级更高
  • 当事件都没有被View或者ViewGroup消费的话,使用Activity的onTouchEvent进行兜底
  • UP和MOVE 事件并不会重新寻找子view,而是直接分发给接收DOWN事件的view

http://www.ppmy.cn/news/1463743.html

相关文章

贪心算法简单介绍

贪心算法是一种在每一步选择中都采取当前状态下最优或最优近似的选择&#xff0c;以期望最终得到全局最优解的算法。贪心算法并不总能得到全局最优解&#xff0c;但在某些问题上&#xff0c;它可以得到全局最优解&#xff0c;并且比动态规划等其他方法更为简单和高效。 贪心算…

MiniCPM-Llama3-V-2_5-int4

MiniCPM-Llama3-V-2_5-int4大模型部署使用环境&#xff1a; python3.8cuda11.8其它要求&#xff0c;按照安装文档要求下载即可 我是在算力平台用4090跑的&#xff0c; GPU 显存&#xff08;8GB&#xff09;可以部署推理 int4 量化版本&#xff0c;如果推理非量化版本需要更高显…

当HR问你是否单身时,该怎么回答?

知识星球&#xff08;星球名&#xff1a;芯片制造与封测技术社区&#xff0c;星球号&#xff1a;63559049&#xff09;里的学员问&#xff1a;我是晶圆厂厂务工程师&#xff0c;最近在面试新工作&#xff0c;但是几乎每家HR都会问我同一个问题&#xff1a;你结婚没有&#xff1…

【知识蒸馏】多任务模型 logit-based 知识蒸馏实战

一、什么是逻辑&#xff08;logit&#xff09;知识蒸馏 Feature-based蒸馏原理是知识蒸馏中的一种重要方法&#xff0c;其关键在于利用教师模型的隐藏层特征来指导学生模型的学习过程。这种蒸馏方式旨在使学生模型能够学习到教师模型在特征提取和表示方面的能力&#xff0c;从…

前后端编程语言和运行环境的理解

我已重新检查了我的回答,并确保信息的准确性。以下是常用的编程语言,以及它们通常用于前端或后端开发,以及相应的框架和运行环境: 前端开发 JavaScript 框架:React, Angular, Vue.js, Ember.js, Backbone.js运行环境:Web 浏览器HTML (HyperText Markup Language) 不是编…

Linux--构建进程池

目录 1.进程池 1.1.我们先完成第一步&#xff0c;创建子进程和信道 1.2. 通过channel控制&#xff0c;发送任务 1.3回收管道和子进程 1.4进行测试 1.5完整代码 1.进程池 进程池其产生原因主要是为了优化大量任务需要多进程完成时频繁创建和删除进程所带来的资源消耗&#…

查询指定会话免打扰

查询指定用户&#xff08;requestId) 为指定会话&#xff08;targetId&#xff09;的设置的免打扰状态。 提示 该设置为用户级别设置。对应的设置接口详见设置指定会话免打扰。 请求方法 POST&#xff1a; https://数据中心域名/conversation/notification/get.json 频率限…

白酒:不同产地白酒的风格特点与比较

云仓酒庄豪迈白酒&#xff0c;作为中国白酒的一部分&#xff0c;其风格特点深受产区的影响。不同产地的白酒&#xff0c;由于自然环境、酿造工艺等因素的差异&#xff0c;形成了各自与众不同的风味和特点。下面让云仓酒庄豪迈白酒来比较一下不同产地白酒的风格特点。 首先&…