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中会通过QueuedInputEvent的enqueueInputEvent将事件加入队列中再处理
//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方法中的内容,我们得出整个链条的结构
分发阶段就会从第一个创建的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层的传递,这一篇我们接着探索input事件在应用中的传递与处理,我们将按键事件和触摸事件分开梳理,这一篇就只涉及按键事件。 一、事件的接收 从前面的…...
我在VScode学Java(Java一维数组)
我的个人博客主页:如果\真能转义1️⃣说1️⃣的博客主页 关于Java基本语法学习---->可以参考我的这篇博客:(我在Vscode学Java) 我在VScode学Java(Java一维数组) Java 一维数组 声明数组:先声明,后使用 动态分配内…...
不能使用chatGPT?这3个平替甚至比chatGPT更强
不能使用chatGPT?这3个平替甚至比chatGPT更强 chatGPT,一款由OpenAI开发的新型AI聊天机器人,正在势如破竹地改变着许多人的工作和生活方式。作为一款基于大语言模型的聊天机器人,chatGPT能够理解自然语言并进行人机对话。与传统的…...
基于SLM调制器,MIT研发高效率全息显示方案
此前,青亭网曾报道过NVIDIA、三星、剑桥大学等对空间光调制器(SLM)全息方案的探索。空间光调制器可调节光波的空间分布,在电驱动信号控制下,可改变光在空间中传播的振幅、强度、相位、偏振态等特性,从而形成…...
【Docker】镜像与docker数据卷
文章目录 一、镜像1、镜像2、镜像原理之联合文件系统3、镜像原理之分层4、commit镜像 二、数据卷1、数据卷2、-v使用数据卷3、实战: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)算法是一种最简单,也是一个很实用的机器学习的…...
函函函函函函函函函函函数——two
🤩本文作者:大家好,我是paperjie,感谢你阅读本文,欢迎一建三连哦。 🥰内容专栏:这里是《C知识系统分享》专栏,笔者用重金(时间和精力)打造,基础知识一网打尽,…...
SpringCloud学习笔记06
九十五、Cloud Alibaba简介 0、why会出现SpringCloud alibaba Spring Cloud Netflix项目进入维护模式 1、是什么 官网: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. 一家农业设备制造商因一个缺陷部件而召回数千个产品。这个问题导致许多客户不满,公司花费 500 万美元来修理和更换零件。哪一种成本预算类型可以防止这个问题? 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 大疆 一面(50min) 二面(30min) 三面(30min) 2 华为 一面(30min) 二面(30min) 三面(30min) 3 海康 一面(…...
五大新兴产业中,有三个中国出口全球占比居首-机器视觉工程师正处于需求旺盛阶段
五大新兴产业包含生物保健和电动汽车,新一代半导体、新一代显示器、二次电池。 在五大新兴产业中的三大领域——新一代半导体、新一代显示器、二次电池,中国对外出口在全球所占比重最高。 电动汽车,汽车行业一直对机器视觉工程师有着强烈的需求,无论比亚迪,特斯拉等等…...
网络安全监管
网络安全监管 网络安全法律体系建设计算机犯罪、信息安全等基本概念我国立法体系及网络安全法我国的立法体系网络安全法出台背景基本概念安全法主要结构第一章 总则第二章 网络安全支持与促进第三章 网络运行安全第四章 网络信息安全第五章 监测预警与应急处置第六章 法律责任 …...
【code review】代码评审的18个军规(建议收藏)
文章目录 背景1. 添加必要的注释2.日志打印规范3. 命名规范4.参数校验5. 判空处理6. 异常处理规范7. 模块化,可扩展性8. 并发控制规范9. 单元测试规范10. 代码格式规范11. 接口兼容性12. 程序逻辑是否清晰,主次是否够分明13. 安全规范14. 事务控制规范15. 幂等处理规…...
PyQt5桌面应用开发(5):对话框
本文目录 PyQt5桌面应用系列对话框QDialogQDialog的基本用法按钮组 QMessageBox综合展示的例子结论 PyQt5桌面应用系列 PyQt5桌面应用开发(1):需求分析 PyQt5桌面应用开发(2):事件循环 PyQt5桌面应用开发&a…...
整洁的代码
文章目录 为什么要写整洁的代码什么是整洁的代码可读性运行效率扩展性 怎么写整洁的代码注释&命名函数&类代码结构 为什么要写整洁的代码 为什么要写整洁的代码,回答这个问题之前,也许应该想想写糟糕的代码的原因 是想快点完成吗?还是要赶时间吗?有可能.或许你觉得…...
Redis集群常用命令及说明
一、集群的特点 1、集群架构特点 (1)所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽; (2)节点的fail是通过集群中超过半数的节点检测失效时才生效…...
使用edge浏览器,白嫖ChatGPT的保姆级教程来了
前言 嗨,大家好,我是希留,一个被迫致力于全栈开发的老菜鸟。 人工智能大浪潮已经来临,对于ChatGPT,我觉得任何一个玩互联网的人,都应该重视起来,用起来。但是国内使用需要解决科学上网、注册、…...
新人入职,都用这三招,让你安全度过试用期
刚入职工作 3招让你安全度过试用期 给新手小伙伴们分享几招 让你们能在试用期的时候平滑去度过 那么第一第一点就是 能自己解决的千万不要去问 千万不要去问 因为往往我们在去面试的时候 我们往往都是备足了很多的资料 备足了很多的面试题库 然后呢 你在给人家面试的时候总有一…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
基于大模型的 UI 自动化系统
基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...
【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...
在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能
下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能,包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...
Python:操作 Excel 折叠
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...
前端倒计时误差!
提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...
前端开发面试题总结-JavaScript篇(一)
文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包(Closure)?闭包有什么应用场景和潜在问题?2.解释 JavaScript 的作用域链(Scope Chain) 二、原型与继承3.原型链是什么?如何实现继承&a…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...
