当前位置: 首页 > news >正文

Input事件在应用中的传递(一)

Input事件在应用中的传递(一)

hongxi.zhu 2023-4-25

前面我们已经梳理了input事件在native层的传递,这一篇我们接着探索input事件在应用中的传递与处理,我们将按键事件和触摸事件分开梳理,这一篇就只涉及按键事件。

一、事件的接收

从前面的篇幅我们知道,framework native层InputDispatcher向应用通过socket方式发送事件,应用的Looper 通过epoll方式监听sockcet的fd, 当应用的socket变为可读时(例如,它有可读事件),Looper将回调handleEvent。 此时,应用应读取已进入套接字的事件。 只要socket中有未读事件,函数 handleEvent 就会继续触发。(这个event不是真正的输入事件,只是Looper的状态event)

//frameworks/base/core/jni/android_view_InputEventReceiver.cppint NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {// Allowed return values of this function as documented in LooperCallback::handleEventconstexpr int REMOVE_CALLBACK = 0;constexpr int KEEP_CALLBACK = 1;//注意:下面这个event不是真正的输入事件,只是Looper的状态eventif (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {//当inputdispatcher异常导致socket被关闭或者目标窗口正在被移除或者传递窗口时输入法,但是输入法正在关闭时会直接抛弃这个事件// This error typically occurs when the publisher has closed the input channel// as part of removing a window or finishing an IME session, in which case// the consumer will soon be disposed as well.if (kDebugDispatchCycle) {ALOGD("channel '%s' ~ Publisher closed input channel or an error occurred. events=0x%x",getInputChannelName().c_str(), events);}return REMOVE_CALLBACK;}//如果是输入事件,即是framework传递过来的事件时需要处理时if (events & ALOOPER_EVENT_INPUT) {JNIEnv* env = AndroidRuntime::getJNIEnv();status_t status = consumeEvents(env, false /*consumeBatches*/, -1, nullptr);mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");return status == OK || status == NO_MEMORY ? KEEP_CALLBACK : REMOVE_CALLBACK;}//如果已处理的事件需要告知inputdispatcher这个事件已处理时if (events & ALOOPER_EVENT_OUTPUT) {const status_t status = processOutboundEvents();if (status == OK || status == WOULD_BLOCK) {return KEEP_CALLBACK;} else {return REMOVE_CALLBACK;}}ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event.  events=0x%x",getInputChannelName().c_str(), events);return KEEP_CALLBACK;
}

handleEvent区分是本次Looper获取到的event, 是需要系统处理接收输入事件,还是需要回复给InputDispatcher事件处理结束的event,如果是需要处理的输入事件,就调用consumeEvents消费这个事件。(注意:这个event不是真正的输入事件,只是Looper的状态case)

//frameworks/base/core/jni/android_view_InputEventReceiver.cppstatus_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {...ScopedLocalRef<jobject> receiverObj(env, nullptr);bool skipCallbacks = false;for (;;) {uint32_t seq;InputEvent* inputEvent;//真正的去获取socket发过来的事件,并构建成具体的某种InputEvent,例如KeyEventstatus_t status = mInputConsumer.consume(&mInputEventFactory,consumeBatches, frameTime, &seq, &inputEvent);if (status != OK && status != WOULD_BLOCK) {ALOGE("channel '%s' ~ Failed to consume input event.  status=%s(%d)",getInputChannelName().c_str(), statusToString(status).c_str(), status);return status;}...

consumeEvents中我们才开始真正的拿着对应的socket fd去读取socket的msg, 具体读取会调用InputConsumer::consume

//frameworks/native/libs/input/InputTransport.cppstatus_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consumeBatches,nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {...*outSeq = 0;*outEvent = nullptr;// Fetch the next input message.// Loop until an event can be returned or no additional events are received.while (!*outEvent) {  //获取到一次真正的事件就退出if (mMsgDeferred) {...} else {// Receive a fresh message.status_t result = mChannel->receiveMessage(&mMsg);  //通过InputChannel来接收socket中真正的InputMessage(描述事件的结构体)...}...}}return OK;
}

InputConsumer::consume中获取事件实际上是通过InputChannel去读取

frameworks/native/libs/input/InputTransport.cppstatus_t InputChannel::receiveMessage(InputMessage* msg) {ssize_t nRead;do {nRead = ::recv(getFd(), msg, sizeof(InputMessage), MSG_DONTWAIT); //在这里真正的读取socket fd,并将输入事件信息装入msg(InputMessage)} while (nRead == -1 && errno == EINTR);...return OK;  //最后返回OK
}

通过InputChannel去读取真正的事件信息,并装入InputMessage对象,最后返回OK

//frameworks/native/libs/input/InputTransport.cppstatus_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consumeBatches,nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {...*outSeq = 0;*outEvent = nullptr;// Fetch the next input message.// Loop until an event can be returned or no additional events are received.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);  //通过InputChannel来接收socket中真正的InputMessage(描述事件的结构体)if (result == OK) {mConsumeTimes.emplace(mMsg.header.seq, systemTime(SYSTEM_TIME_MONOTONIC));}if (result) {  //result = OK = 0 ,所以并不会进入批处理流程// Consume the next batched event unless batches are being held for later.if (consumeBatches || result != WOULD_BLOCK) {result = consumeBatch(factory, frameTime, outSeq, outEvent);if (*outEvent) {if (DEBUG_TRANSPORT_ACTIONS) {ALOGD("channel '%s' consumer ~ consumed batch event, seq=%u",mChannel->getName().c_str(), *outSeq);}break;}}return result;}}switch (mMsg.header.type) {case InputMessage::Type::KEY: {KeyEvent* keyEvent = factory->createKeyEvent();  //创建KeyEventif (!keyEvent) return NO_MEMORY;initializeKeyEvent(keyEvent, &mMsg);  //将InputMessage信息填充到keyEvent*outSeq = mMsg.header.seq;*outEvent = keyEvent;  // 返回到上一级使用(keyEvent 继承于InputEvent)if (DEBUG_TRANSPORT_ACTIONS) {ALOGD("channel '%s' consumer ~ consumed key event, seq=%u",mChannel->getName().c_str(), *outSeq);}break;}...}}return OK;
}

回到上一级,InputConsumer::consume中,receiveMessage中获取到的InputMessage在这里转化为对应的event类型,对应的按键事件就是KeyEvent,*outEvent = keyEvent返回到前面的NativeInputEventReceiver::consumeEvents

//frameworks/base/core/jni/android_view_InputEventReceiver.cppstatus_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {...for (;;) {uint32_t seq;InputEvent* inputEvent;//真正的去获取socket发过来的事件,并构建成具体的某种InputEvent,例如KeyEventstatus_t status = mInputConsumer.consume(&mInputEventFactory,consumeBatches, frameTime, &seq, &inputEvent);...if (!skipCallbacks) {jobject inputEventObj;switch (inputEvent->getType()) {case AINPUT_EVENT_TYPE_KEY:if (kDebugDispatchCycle) {ALOGD("channel '%s' ~ Received key event.", getInputChannelName().c_str());}inputEventObj = android_view_KeyEvent_fromNative(env,static_cast<KeyEvent*>(inputEvent));  //将consume()中拿到的inputEvent转为相应的KeyEvent(在内部我们创建的就是KeyEvent)break;...if (inputEventObj) {...env->CallVoidMethod(receiverObj.get(),gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);  //jni调用InputEventReceiver.java中的InputEventReceiver,将事件传递到java的世界...env->DeleteLocalRef(inputEventObj);}...}}
}

通过consume中拿到inputEvent,然后转为KeyEvent,最通过Jni的方式调用java中的InputEventReceiver的方法dispatchInputEvent,正式开始事件的分发。

//frameworks/base/core/java/android/view/InputEventReceiver.javapublic abstract class InputEventReceiver {...// Called from native code.@SuppressWarnings("unused")@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)private void dispatchInputEvent(int seq, InputEvent event) {mSeqMap.put(event.getSequenceNumber(), seq);onInputEvent(event);}...
}

InputEventReceiver是一个抽象类,但是对应的dispatchInputEvent方法,它的子类WindowInputEventReceiver并没有实现,所以native层调用父类的InputEventReceiver的方法,这个方法中接着调用了onInputEvent接着处理。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");List<InputEvent> processedEvents;try {//对M版本之前的触摸事件的兼容处理,按键事件不涉及, return nullprocessedEvents =mInputCompatProcessor.processInputEventForCompatibility(event); } finally {Trace.traceEnd(Trace.TRACE_TAG_VIEW);}if (processedEvents != null) {if (processedEvents.isEmpty()) {// InputEvent consumed by mInputCompatProcessorfinishInputEvent(event, true);} else {for (int i = 0; i < processedEvents.size(); i++) {enqueueInputEvent(processedEvents.get(i), this,QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true);}}} else {  //因为上面返回null 所以走到这里//在这里将自己this传入//processImmediately 为true意味着需要马上处理,而不是延迟处理       enqueueInputEvent(event, this, 0, true);}}...

onInputEvent中会通过QueuedInputEventenqueueInputEvent将事件加入队列中再处理

//frameworks/base/core/java/android/view/ViewRootImpl.java@UnsupportedAppUsagevoid enqueueInputEvent(InputEvent event,InputEventReceiver receiver, int flags, boolean processImmediately) {QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);  //将事件加入队列,确保事件的有序处理if (event instanceof MotionEvent) {...} else if (event instanceof KeyEvent) { //如果案件事件是一个key的canceled事件KeyEvent ke = (KeyEvent) event;if (ke.isCanceled()) {EventLog.writeEvent(EventLogTags.VIEW_ENQUEUE_INPUT_EVENT, "Key - Cancel",getTitle().toString());}}// 无论时间戳如何,始终按顺序排列输入事件。// 我们这样做是因为应用本身或 IME 可能会注入事件,// 我们希望确保注入的按键按照接收到的顺序进行处理,不能仅仅通过时间戳的前后来确定顺序。// Always enqueue the input event in order, regardless of its time stamp.// We do this because the application or the IME may inject key events// in response to touch events and we want to ensure that the injected keys// are processed in the order they were received and we cannot trust that// the time stamp of injected events are monotonic.QueuedInputEvent last = mPendingInputEventTail;if (last == null) {mPendingInputEventHead = q;mPendingInputEventTail = q;} else {last.mNext = q;mPendingInputEventTail = q;}mPendingInputEventCount += 1;Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,mPendingInputEventCount);if (processImmediately) {doProcessInputEvents();  //前面传进来的processImmediately = true所以走这里处理} else {scheduleProcessInputEvents();}}

为什么需要加入队列处理?一般来说,当InputDispatcher发送一个事件给应用,应用需要处理完并反馈给InputDispatcher,后者才会发送下一个事件,本身操作流程就是串行的,看起来是不需要队列来保证串行处理。但是不要忘记,除了来自底层驱动的事件外,应用和IME都是可以往应用进程注入事件的,那么就需要保证处理的顺序。那么下一步就是从队头依次拿出事件来分发了,对应方式是doProcessInputEvents

//frameworks/base/core/java/android/view/ViewRootImpl.javavoid doProcessInputEvents() {// Deliver all pending input events in the queue.while (mPendingInputEventHead != null) {QueuedInputEvent q = mPendingInputEventHead;  //从队列中拿出数据分发,确保有序mPendingInputEventHead = q.mNext;if (mPendingInputEventHead == null) {mPendingInputEventTail = null;}q.mNext = null;mPendingInputEventCount -= 1;Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,mPendingInputEventCount);mViewFrameInfo.setInputEvent(mInputEventAssigner.processEvent(q.mEvent));deliverInputEvent(q);  //开始分发事件}// We are done processing all input events that we can process right now// so we can clear the pending flag immediately.if (mProcessInputEventsScheduled) {mProcessInputEventsScheduled = false;mHandler.removeMessages(MSG_PROCESS_INPUT_EVENTS);}}

前面将事件入队,然后在doProcessInputEvents就开始从队头拿出并通过deliverInputEvent开始分发

//frameworks/base/core/java/android/view/ViewRootImpl.javaprivate void deliverInputEvent(QueuedInputEvent q) {Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent",q.mEvent.getId());...try {...InputStage stage;if (q.shouldSendToSynthesizer()) {stage = mSyntheticInputStage;} else {//如果忽略输入法窗口则从mFirstPostImeInputStage阶段开始分发,否则从mFirstInputStage开始stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;}...if (stage != null) {handleWindowFocusChanged();  //在分发前确认是否焦点窗口变化了,如果变化就需要更新焦点的信息stage.deliver(q);  //调用对应的stage阶段的deliver方法分发事件} else {finishInputEvent(q);}} finally {Trace.traceEnd(Trace.TRACE_TAG_VIEW);}}

把事件从拿出,下一步就是往view或者IME分发,分发的过程这里会分为多个阶段(InputStage)来顺序执行, 这些阶段在ViewRootImpl中setView时会指定

//frameworks/base/core/java/android/view/ViewRootImpl.java/*** We have one child*/public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,int userId) {synchronized (this) {if (mView == null) {...// 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);mFirstInputStage = nativePreImeStage;mFirstPostImeInputStage = earlyPostImeStage;mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;}}}

InputStage这里采用责任链的设计模式,从抽象类InputStage内容可以知道,每一个子类都会将next指向下一个stage子类对象

//frameworks/base/core/java/android/view/ViewRootImpl.javaabstract class InputStage {private final InputStage mNext;protected static final int FORWARD = 0;protected static final int FINISH_HANDLED = 1;protected static final int FINISH_NOT_HANDLED = 2;private String mTracePrefix;/*** Creates an input stage.* 将所有的阶段都组成一个链表,next指向下一个阶段* @param next The next stage to which events should be forwarded.*/public InputStage(InputStage next) {mNext = next;}...

setView方法中的内容,我们得出整个链条的结构

NativePreImeInputStage
ViewPreImeInputStage
ImeInputStage
EarlyPostImeInputStage
NativePostImeInputStage
ViewPostImeInputStage
SyntheticInputStage

分发阶段就会从第一个创建的stage子类开始执行到最后一个stage子类,无论要不要处理,都要从链表的头传递到尾。
回到deliverInputEvent方法中stage.deliver(q)正式进入stage的分发中,观察下完整的一个stage的处理流程

//frameworks/base/core/java/android/view/ViewRootImpl.java/*** Delivers an event to be processed.*/public final void deliver(QueuedInputEvent q) {if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {  //如果上一stage中事件被处理(FLAG_FINISHED)那么本stage就不会再处理(onProcess),直接传递到下一个stage(无论是要处理,链表都要走完)forward(q);} else if (shouldDropInputEvent(q)) {finish(q, false);} else {traceEvent(q, Trace.TRACE_TAG_VIEW);final int result;try {result = onProcess(q);  //如果前面的阶段没有被处理,本stage就需要走处理流程} finally {Trace.traceEnd(Trace.TRACE_TAG_VIEW);}apply(q, result);  //判断是否需要下一个阶段走处理流程}}/*** Marks the input event as finished then forwards it to the next stage.*  如果事件在当前阶段被结束,q.mFlags被标记为FLAG_FINISHED,并通过forward(q)传递给下一个阶段*/protected void finish(QueuedInputEvent q, boolean handled) {q.mFlags |= QueuedInputEvent.FLAG_FINISHED;if (handled) {q.mFlags |= QueuedInputEvent.FLAG_FINISHED_HANDLED;}forward(q);}/*** Forwards the event to the next stage.* 往下一个阶段分发*/protected void forward(QueuedInputEvent q) {onDeliverToNext(q);// 继续往下一个阶段传递}/*** Applies a result code from {@link #onProcess} to the specified event.* 判断是否需要继续接着往下一个阶段分发*/protected void apply(QueuedInputEvent q, int result) {if (result == FORWARD) {  //如果上一个阶段还没处理这个事件,则继续往下一个阶段分发处理forward(q);} else if (result == FINISH_HANDLED) {   //如果事件被处理了,就标记为FLAG_FINISHED|FLAG_FINISHED_HANDLED,然后继续传递给下一个阶段(但不走onProcess()了)finish(q, true);} else if (result == FINISH_NOT_HANDLED) {  //如果事件没有被处理则标记为FLAG_FINISHED,然后继续传递给下一个阶段(但不走onProcess()了)finish(q, false);} else {throw new IllegalArgumentException("Invalid result: " + result);}}/*** Called when an event is ready to be processed.* @return A result code indicating how the event was handled.*/protected int onProcess(QueuedInputEvent q) {return FORWARD;}/*** Called when an event is being delivered to the next stage.* 继续执行下一阶段的deliver*/protected void onDeliverToNext(QueuedInputEvent q) {if (DEBUG_INPUT_STAGES) {Log.v(mTag, "Done with " + getClass().getSimpleName() + ". " + q);}if (mNext != null) {mNext.deliver(q);  //如果下一阶段不为空就继续执行下一阶段的deliver,继续往下一阶段传递} else {finishInputEvent(q);}}

具体如流程图:
在这里插入图片描述
NativePreImeInputStage开始deliver,事件经过每一个stage, 如果该事件没有被处理(标记为)FLAG_FINISHED或者该事件应该被抛弃(shouldDropInputEvent),那么就应该传给本阶段(stage)处理(onProcess),按照这个逻辑一直跑完整个链表。

在这里阶段里我们本篇比较关心往View树分发的阶段,即ViewPostImeInputStage

//frameworks/base/core/java/android/view/ViewRootImpl.java/*** Delivers post-ime input events to the view hierarchy.*/final class ViewPostImeInputStage extends InputStage {public ViewPostImeInputStage(InputStage next) {super(next);}// 子类重写了onProcess方法@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);}}}...private int processKeyEvent(QueuedInputEvent q) {final KeyEvent event = (KeyEvent)q.mEvent;if (mUnhandledKeyManager.preViewDispatch(event)) {return FINISH_HANDLED;}// 往view树分发事件// Deliver the key to the view hierarchy.if (mView.dispatchKeyEvent(event)) {  // mView实际上是DecorView, 在addView时添加return FINISH_HANDLED;}...}

ViewPostImeInputStage的处理在onProcess方法,其中最关键的地方就是mView.dispatchKeyEvent(event)mView
实际上是传入的DecorView,具体可以查看应用启动过程流程。通过DecorView的dispatchKeyEvent开始事件在View树的传递。

//frameworks/base/core/java/com/android/internal/policy/DecorView.java@Overridepublic boolean dispatchKeyEvent(KeyEvent event) {final int keyCode = event.getKeyCode();final int action = event.getAction();final boolean isDown = action == KeyEvent.ACTION_DOWN;//快捷键处理if (isDown && (event.getRepeatCount() == 0)) {// First handle chording of panel key: if a panel key is held// but not released, try to execute a shortcut in it.if ((mWindow.mPanelChordingKey > 0) && (mWindow.mPanelChordingKey != keyCode)) {boolean handled = dispatchKeyShortcutEvent(event);if (handled) {return true;}}// If a panel is open, perform a shortcut on it without the// chorded panel keyif ((mWindow.mPreparedPanel != null) && mWindow.mPreparedPanel.isOpen) {if (mWindow.performPanelShortcut(mWindow.mPreparedPanel, keyCode, event, 0)) {return true;}}}//mWindow是PhoneWindow的实例if (!mWindow.isDestroyed()) {// 这个cb实际上是Activity对象,(当调Activity的attach方法时, 通过mWindow.setCallback(this)传入)final Window.Callback cb = mWindow.getCallback();// 因为Activity这里不为null,所以会掉Activity的dispatchKeyEventfinal boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event): super.dispatchKeyEvent(event);if (handled) {return true;}}return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event): mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);}

这里关键的是这个cb对象,根据应用的启动流程可知,这个cb是当调Activity的attach方法时, 通过mWindow.setCallback(this)传入的Activity对象,且不为null,所以事件会传到Activity,调用它的dispatchKeyEvent方法。

//frameworks/base/core/java/android/app/Activity.java/*** Called to process key events.  You can override this to intercept all* key events before they are dispatched to the window.  Be sure to call* this implementation for key events that should be handled normally.** @param event The key event.** @return boolean Return true if this event was consumed.*/public boolean dispatchKeyEvent(KeyEvent event) {onUserInteraction();  //通知通知栏进行相应的变化// Let action bars open menus in response to the menu key prioritized over// the window handling it// MENU键优先给ActionBar处理final int keyCode = event.getKeyCode();if (keyCode == KeyEvent.KEYCODE_MENU &&mActionBar != null && mActionBar.onMenuKeyEvent(event)) {return true;}// 这里的getWindow()拿到的是PhoneWindow对象(在Activity的attach方法中创建的)Window win = getWindow();if (win.superDispatchKeyEvent(event)) {  //这里会先分发给PhoneWindow, 实际上PhoneWindow会调DecorView的superDispatchKeyEventreturn true;}View decor = mDecor;if (decor == null) decor = win.getDecorView();return event.dispatch(this, decor != null? decor.getKeyDispatcherState() : null, this);}

从这里可以看出,Activity会调PhoneWindow的superDispatchKeyEvent将事件发给PhoneWindow处理

//frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java@Overridepublic boolean superDispatchKeyEvent(KeyEvent event) {return mDecor.superDispatchKeyEvent(event); // 实际上调的是DecorView的方法,让DecorView分发}
//frameworks/base/core/java/com/android/internal/policy/DecorView.java/** @hide */
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {...public boolean superDispatchKeyEvent(KeyEvent event) {...if (super.dispatchKeyEvent(event)) {return true;}return (getViewRootImpl() != null) && getViewRootImpl().dispatchUnhandledKeyEvent(event);}...
}

实际上PhoneWindow会调DecorView的superDispatchKeyEvent,最终又回到DecorView, 为什么这样流转呢?仔细观察DecorView是继承于FrameLayout,而FrameLayout继承于ViewGroup,那它就是树中最顶端的ViewGroup, 事件应该从它开始分发。

//frameworks/base/core/java/android/view/ViewGroup.java@Overridepublic boolean dispatchKeyEvent(KeyEvent event) {...if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))  //是否焦点是自己(ViewGroup)== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {if (super.dispatchKeyEvent(event)) {  //调用父类的方法处理,ViewGroup也是继承于Viewreturn true;}} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)  //是否焦点View是自己的子view或者子ViewGroup== PFLAG_HAS_BOUNDS) {if (mFocused.dispatchKeyEvent(event)) {  //传递给自己子view或者viewgroup处理return true;}}...return false;}

这里会根据判断焦点view来决定分发给谁,首先判断自己本身(ViewGroup)是不是焦点View,如果是则调用父类View的dispatchKeyEvent方法处理按键事件;如果焦点view在自己的子view或者子viewgroup上,则继续往下分发。如果是子viewgroup那么和上面流程一样继续判断是否继续往下,如果是子view,就调用view的dispatchKeyEvent处理,所以最终都是View的dispatchKeyEvent处理。

// frameworks/base/core/java/android/view/View.java/*** Dispatch a key event to the next view on the focus path. This path runs* from the top of the view tree down to the currently focused view. If this* view has focus, it will dispatch to itself. Otherwise it will dispatch* the next node down the focus path. This method also fires any key* listeners.** @param event The key event to be dispatched.* @return True if the event was handled, false otherwise.*/public boolean dispatchKeyEvent(KeyEvent event) {...// Give any attached key listener a first crack at the event.//noinspection SimplifiableIfStatement// ListenerInfo是管理各种监听器的类,它持有监听器的实例,例如:OnClickListener、OnTouchListenerListenerInfo li = mListenerInfo;// 如果应用注册了监听器mOnKeyListener,那么就优先调用mOnKeyListener.onKey回调if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {return true;}//如果上述未处理,则默认走KeyEvent的dispatch来处理按键事件if (event.dispatch(this, mAttachInfo != null? mAttachInfo.mKeyDispatchState : null, this)) {return true;}...return false;}

相关文章:

Input事件在应用中的传递(一)

Input事件在应用中的传递(一) hongxi.zhu 2023-4-25 前面我们已经梳理了input事件在native层的传递&#xff0c;这一篇我们接着探索input事件在应用中的传递与处理&#xff0c;我们将按键事件和触摸事件分开梳理&#xff0c;这一篇就只涉及按键事件。 一、事件的接收 从前面的…...

我在VScode学Java(Java一维数组)

我的个人博客主页&#xff1a;如果\真能转义1️⃣说1️⃣的博客主页 关于Java基本语法学习---->可以参考我的这篇博客&#xff1a;(我在Vscode学Java) 我在VScode学Java(Java一维数组&#xff09; Java 一维数组 声明数组&#xff1a;先声明&#xff0c;后使用 动态分配内…...

不能使用chatGPT?这3个平替甚至比chatGPT更强

不能使用chatGPT&#xff1f;这3个平替甚至比chatGPT更强 chatGPT&#xff0c;一款由OpenAI开发的新型AI聊天机器人&#xff0c;正在势如破竹地改变着许多人的工作和生活方式。作为一款基于大语言模型的聊天机器人&#xff0c;chatGPT能够理解自然语言并进行人机对话。与传统的…...

基于SLM调制器,MIT研发高效率全息显示方案

此前&#xff0c;青亭网曾报道过NVIDIA、三星、剑桥大学等对空间光调制器&#xff08;SLM&#xff09;全息方案的探索。空间光调制器可调节光波的空间分布&#xff0c;在电驱动信号控制下&#xff0c;可改变光在空间中传播的振幅、强度、相位、偏振态等特性&#xff0c;从而形成…...

【Docker】镜像与docker数据卷

文章目录 一、镜像1、镜像2、镜像原理之联合文件系统3、镜像原理之分层4、commit镜像 二、数据卷1、数据卷2、-v使用数据卷3、实战&#xff1a;MySQL 同步数据4、docker volume相关指令5、匿名和具名挂载6、数据卷之Dockerfile7、数据卷容器 一、镜像 1、镜像 镜像是一种轻量级…...

机器学习小结之KNN算法

文章目录 前言一、概念1.1 机器学习基本概念1.2 k 值1.3 距离度量1.4 加权方式 二、实现2.1 手写实现2.2 调库 Scikit-learn2.3 测试自己的数据 三、总结3.1 分析3.2 KNN 优缺点 参考 前言 ​ KNN (K-Nearest Neighbor)算法是一种最简单&#xff0c;也是一个很实用的机器学习的…...

函函函函函函函函函函函数——two

&#x1f929;本文作者&#xff1a;大家好&#xff0c;我是paperjie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 &#x1f970;内容专栏&#xff1a;这里是《C知识系统分享》专栏&#xff0c;笔者用重金(时间和精力)打造&#xff0c;基础知识一网打尽&#xff0c;…...

SpringCloud学习笔记06

九十五、Cloud Alibaba简介 0、why会出现SpringCloud alibaba Spring Cloud Netflix项目进入维护模式 1、是什么 官网&#xff1a;spring-cloud-alibaba/README-zh.md at 2.2.x alibaba/spring-cloud-alibaba GitHub 2、能干嘛 3、去哪下 spring-cloud-alibaba/README-…...

学系统集成项目管理工程师(中项)系列14_采购管理

1. 概念和术语 1.1. 采购是从项目团队外部获得产品、服务或成果的完整的购买过程 1.2. 三大类 1.2.1. 工程 1.2.2. 产品/货物 1.2.3. 服务 2. 主要过程 2.1. 编制采购管理计划 2.2. 实施采购 2.3. 控制采购 2.4. 结束采购 3. 合同 3.1. 包括买方和卖方之间的法律文…...

PMP课堂模拟题目及解析(第3期)

21. 一家农业设备制造商因一个缺陷部件而召回数千个产品。这个问题导致许多客户不满&#xff0c;公司花费 500 万美元来修理和更换零件。哪一种成本预算类型可以防止这个问题&#xff1f; A. 非一致性成本 B. 一致性成本 C. 矩阵图 D. 多标准决策分析 22. 一位团队成员…...

华为OD机试 - 微服务的集成测试( Python)

题目描述 现在有n个容器服务,服务的启动可能有一定的依赖性(有些服务启动没有依赖),其次服务自身启动加载会消耗一些时间。 给你一个 n x n 的二维矩阵useTime,其中 useTime[i][i]=10 表示服务i自身启动加载需要消耗10s useTime[i][j] = 1 表示服务i启动依赖服务j启动完…...

SLAM面试笔记(4) — 企业面试汇总

目录 1 大疆 一面&#xff08;50min&#xff09; 二面&#xff08;30min&#xff09; 三面&#xff08;30min&#xff09; 2 华为 一面&#xff08;30min&#xff09; 二面&#xff08;30min&#xff09; 三面&#xff08;30min&#xff09; 3 海康 一面&#xff08;…...

五大新兴产业中,有三个中国出口全球占比居首-机器视觉工程师正处于需求旺盛阶段

​五大新兴产业包含生物保健和电动汽车,新一代半导体、新一代显示器、二次电池。 在五大新兴产业中的三大领域——新一代半导体、新一代显示器、二次电池,中国对外出口在全球所占比重最高。 电动汽车,汽车行业一直对机器视觉工程师有着强烈的需求,无论比亚迪,特斯拉等等…...

网络安全监管

网络安全监管 网络安全法律体系建设计算机犯罪、信息安全等基本概念我国立法体系及网络安全法我国的立法体系网络安全法出台背景基本概念安全法主要结构第一章 总则第二章 网络安全支持与促进第三章 网络运行安全第四章 网络信息安全第五章 监测预警与应急处置第六章 法律责任 …...

【code review】代码评审的18个军规(建议收藏)

文章目录 背景1. 添加必要的注释2.日志打印规范3. 命名规范4.参数校验5. 判空处理6. 异常处理规范7. 模块化&#xff0c;可扩展性8. 并发控制规范9. 单元测试规范10. 代码格式规范11. 接口兼容性12. 程序逻辑是否清晰,主次是否够分明13. 安全规范14. 事务控制规范15. 幂等处理规…...

PyQt5桌面应用开发(5):对话框

本文目录 PyQt5桌面应用系列对话框QDialogQDialog的基本用法按钮组 QMessageBox综合展示的例子结论 PyQt5桌面应用系列 PyQt5桌面应用开发&#xff08;1&#xff09;&#xff1a;需求分析 PyQt5桌面应用开发&#xff08;2&#xff09;&#xff1a;事件循环 PyQt5桌面应用开发&a…...

整洁的代码

文章目录 为什么要写整洁的代码什么是整洁的代码可读性运行效率扩展性 怎么写整洁的代码注释&命名函数&类代码结构 为什么要写整洁的代码 为什么要写整洁的代码,回答这个问题之前,也许应该想想写糟糕的代码的原因 是想快点完成吗?还是要赶时间吗?有可能.或许你觉得…...

Redis集群常用命令及说明

一、集群的特点 1、集群架构特点 &#xff08;1&#xff09;所有的redis节点彼此互联&#xff08;PING-PONG机制&#xff09;&#xff0c;内部使用二进制协议优化传输速度和带宽&#xff1b; &#xff08;2&#xff09;节点的fail是通过集群中超过半数的节点检测失效时才生效…...

使用edge浏览器,白嫖ChatGPT的保姆级教程来了

前言 嗨&#xff0c;大家好&#xff0c;我是希留&#xff0c;一个被迫致力于全栈开发的老菜鸟。 人工智能大浪潮已经来临&#xff0c;对于ChatGPT&#xff0c;我觉得任何一个玩互联网的人&#xff0c;都应该重视起来&#xff0c;用起来。但是国内使用需要解决科学上网、注册、…...

新人入职,都用这三招,让你安全度过试用期

刚入职工作 3招让你安全度过试用期 给新手小伙伴们分享几招 让你们能在试用期的时候平滑去度过 那么第一第一点就是 能自己解决的千万不要去问 千万不要去问 因为往往我们在去面试的时候 我们往往都是备足了很多的资料 备足了很多的面试题库 然后呢 你在给人家面试的时候总有一…...

Leetcode 3576. Transform Array to All Equal Elements

Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接&#xff1a;3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到&#xf…...

DockerHub与私有镜像仓库在容器化中的应用与管理

哈喽&#xff0c;大家好&#xff0c;我是左手python&#xff01; Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库&#xff0c;用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...

java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别

UnsatisfiedLinkError 在对接硬件设备中&#xff0c;我们会遇到使用 java 调用 dll文件 的情况&#xff0c;此时大概率出现UnsatisfiedLinkError链接错误&#xff0c;原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用&#xff0c;结果 dll 未实现 JNI 协…...

ArcGIS Pro制作水平横向图例+多级标注

今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作&#xff1a;ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等&#xff08;ArcGIS出图图例8大技巧&#xff09;&#xff0c;那这次我们看看ArcGIS Pro如何更加快捷的操作。…...

【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)

1.获取 authorizationCode&#xff1a; 2.利用 authorizationCode 获取 accessToken&#xff1a;文档中心 3.获取手机&#xff1a;文档中心 4.获取昵称头像&#xff1a;文档中心 首先创建 request 若要获取手机号&#xff0c;scope必填 phone&#xff0c;permissions 必填 …...

Linux --进程控制

本文从以下五个方面来初步认识进程控制&#xff1a; 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程&#xff0c;创建出来的进程就是子进程&#xff0c;原来的进程为父进程。…...

短视频矩阵系统文案创作功能开发实践,定制化开发

在短视频行业迅猛发展的当下&#xff0c;企业和个人创作者为了扩大影响力、提升传播效果&#xff0c;纷纷采用短视频矩阵运营策略&#xff0c;同时管理多个平台、多个账号的内容发布。然而&#xff0c;频繁的文案创作需求让运营者疲于应对&#xff0c;如何高效产出高质量文案成…...

通过 Ansible 在 Windows 2022 上安装 IIS Web 服务器

拓扑结构 这是一个用于通过 Ansible 部署 IIS Web 服务器的实验室拓扑。 前提条件&#xff1a; 在被管理的节点上安装WinRm 准备一张自签名的证书 开放防火墙入站tcp 5985 5986端口 准备自签名证书 PS C:\Users\azureuser> $cert New-SelfSignedCertificate -DnsName &…...

Kafka主题运维全指南:从基础配置到故障处理

#作者&#xff1a;张桐瑞 文章目录 主题日常管理1. 修改主题分区。2. 修改主题级别参数。3. 变更副本数。4. 修改主题限速。5.主题分区迁移。6. 常见主题错误处理常见错误1&#xff1a;主题删除失败。常见错误2&#xff1a;__consumer_offsets占用太多的磁盘。 主题日常管理 …...

Vue 模板语句的数据来源

&#x1f9e9; Vue 模板语句的数据来源&#xff1a;全方位解析 Vue 模板&#xff08;<template> 部分&#xff09;中的表达式、指令绑定&#xff08;如 v-bind, v-on&#xff09;和插值&#xff08;{{ }}&#xff09;都在一个特定的作用域内求值。这个作用域由当前 组件…...