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招让你安全度过试用期 给新手小伙伴们分享几招 让你们能在试用期的时候平滑去度过 那么第一第一点就是 能自己解决的千万不要去问 千万不要去问 因为往往我们在去面试的时候 我们往往都是备足了很多的资料 备足了很多的面试题库 然后呢 你在给人家面试的时候总有一…...
小程序上车,车载小程序的信息安全是否可靠?
随着智能交通和车联网技术的快速发展,越来越多的车载应用程序(APP)进入人们的视野,从而推动了车载业务生态的不断发展。然而,车载应用程序的安全问题也引起了人们的广泛关注。为此,小程序容器技术作为一种有…...
华为OD机试 - 识图谱新词挖掘(Python)
题目描述 小华负责公司知识图谱产品,现在要通过新词挖掘完善知识图谱。 新词挖掘:给出一个待挖掘问题内容字符串Content和一个词的字符串word,找到content中所有word的新词。 新词:使用词word的字符排列形成的字符串。 请帮小华实现新词挖掘,返回发现的新词的数量。 …...
( 数组和矩阵) 378. 有序矩阵中第 K 小的元素 ——【Leetcode每日一题】
❓378. 有序矩阵中第 K 小的元素 难度:中等 给你一个 n x n n x n nxn 矩阵 m a t r i x matrix matrix ,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。 请注意,它是 排序后 的第 k 小元素,而不是第 …...
HBase架构篇 - Hadoop家族的天之骄子HBase
HBase的基本组成结构 表(table) HBase 的数据存储在表中。表名是一个字符串。表由行和列组成。 行(row) HBase 的行由行键(rowkey)和 n 个列(column)组成。行键没有数据类型&…...
STL及常用容器vector、list和deque的介绍
vector和built-in数组类似,它拥有一段连续的内存空间,并且起始地址不变,因此它能非常好的支持随机存取,即[]操作符,即可以以数组下标的方式来访问或遍历。但由于它的内存空间是连续的,所以在中间进行插入和删除会造成内存块的拷贝,另外,当该数组后的内存空间不够时,需…...
SpringBoot统一功能处理(统⼀⽤户登录权限验证、统⼀异常处理、统⼀数据格式封装)
统⼀⽤户登录权限验证 1、最初的用户登录效验:在每个方法里面获取session和 session 中的用户信息,如果存在用户,那么就认为登录成功了,否则就登录失败了。 2、第二版用户登录效验:提供了统一的方法,在每个需要验证的方法中调用…...
华为实习笔试复盘(1)配送站和客户问题
写在前面 自己玩了很多项目,但是最近准备秋招的过程中,发现自己对于算法和编程语言的基本功夫实在是太欠缺了。 投递了华为的实习岗位,4.26参加机考,一做题就发现了自己很多地方都不会。这里写下笔试后的复盘以警醒自己。 题目 …...
alibaba yalantingLibs struct_pack代码梳理
这里写目录标题 struct_pack 接口序列化序列化对象到新字节容器序列化对象到容器尾部将序列化结果保存到指针指向的内存中多参数序列化将序列化结果保存到输出流自定义类型序列化序列化到自定义的输出流 反序列化基本反序列化从指针指向的内存中反序列化反序列化到已有对象多参…...
JavaWeb( 二 ) URL
1.4.URL统一资源定位符 URL代表Uniform Resource Locator 统一资源定位符,也叫 URL地址 。是用于标识和定位Web上资源的地址,通常用于在Web浏览器中访问网站和文件。 URL由若干部分组成,scheme:// host : port / path 例如: htt…...
Python斐波那契数列
斐波那契数列是一个经典的数学问题,在 Python 中可以使用多种方法来实现,下面是几个常见的实现方式: 1. 使用递归 python def fibonacci_recursive(n): if n < 1: return n else: return fibonacci_recursive(n…...
易语言用电脑做网站服务器/网络营销的种类有哪些
stringstr1 Process.GetCurrentProcess().MainModule.FileName; //可获得当前执行的exe的文件名。 stringstr2Environment.CurrentDirectory; //获取和设置当前目录(即该进程从中启动的目录)的完全限定路径。 //备注 按照定义&…...
wordpress出站链接/谷歌商店官网
abstract abstract: 抽象的 1.可以用来修饰:类、方法 2.具体的: abstract修饰类:抽象类 > 此类不能实例化 > 抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程) > 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作 --…...
公司做网站 优帮云/百度推广网址是多少
2013 lost connection to mysql server during query navicat 导入sql大脚本到mysql数据库报错 解决办法: 修改mysql.ini配置文件: max_allowed_packet256M wait_timeout5000posted on 2016-06-18 17:12 NET未来之路 阅读(...) 评论(...) 编辑 收藏 转载于:https://…...
如何做网站的301重定向/新东方雅思培训机构官网
本例介绍《vue.js实战》第五章最后的购物车练习一, 练习1:在当前示例基础上扩展商品列表,新增一项是否选中该商品的功能,总价变为只选中商品的总价,同时提供一个全选按钮 截图如下: html: <div id"app" v-cloak><template v-if"list.length">…...
旅游网站开发毕业论文前言/搜索引擎收录提交入口
用户在使用经典虚拟机时,经常会有如下疑问:门户主板页面中的 SSH/RDP 证书指纹这项信息是怎么来的?用途是什么?为什么有的时候为空?有没有对虚拟机使用有什么影响?以下我们进行一些基本的介绍: …...
上海建设单位工程备案网站/hao123网址导航
从Theano到Lasagne:基于Python的深度学习的框架和库 摘要:最近,深度神经网络以“Deep Dreams”形式在网站中如雨后春笋般出现,或是像谷歌研究原创论文中描述的那样:Inceptionism。在这篇文章中,我们将讨论几…...