码迷,mamicode.com
首页 > 移动开发 > 详细

Android触摸事件分发

时间:2021-01-22 12:02:30      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:delay   virt   ble   ram   last   mes   rect   false   The   

整体分发流程

Android Input Framework 这篇详细讲解了触摸事件从硬件分发到Activity之前的过程。

graph TD; a(Input Hardware)-->b(Kernel/Driver); b-->c(EventHub); c-->d(InputReader); d-->e(InputDispatcher); e-->f(ViewRootImpl); f-->i(DecorView); i-->g(Activity); g-->h(Window); h-->i; i-->j(LinearLayout); j-->k(FrameLayout); k-->l(ContentFrameLayout) l-->m(Child View)

接下来,我们就从EventHub开始一点一点研究事件的分发过程。

EventHub

EventHub.h,这是EventHub.h的源码,以便下面讲解不懂的查阅。

首先,EventHub实现了EventHubInterface接口,EventHubInterface功能如下:

/*
 * Grand Central Station for events.
 *
 * The event hub aggregates input events received across all known input
 * devices on the system, including devices that may be emulated by the simulator
 * environment.  In addition, the event hub generates fake input events to indicate
 * when devices are added or removed.
 *
 * The event hub provides a stream of input events (via the getEvent function).
 * It also supports querying the current actual state of input devices such as identifying
 * which keys are currently down.  Finally, the event hub keeps track of the capabilities of
 * individual input devices, such as their class and the set of key codes that they support.
 */

接收系统内所有已知设备的输入事件(包括模拟设备);当设备添加或者移除时,会生成假消息通知;可以通过getEvent函数获得输入事件;它还支持查询输入设备的当前实际状态,例如标识哪些键当前处于关闭状态。最后,EventHub跟踪各个输入设备的功能,例如它们的类和它们支持的关键代码集。

EventHub所支持的设备如下:

/*
 * Input device classes.
 */
enum {
    /* The input device is a keyboard or has buttons. */
    INPUT_DEVICE_CLASS_KEYBOARD = 0x00000001,

    /* The input device is an alpha-numeric keyboard (not just a dial pad). */
    INPUT_DEVICE_CLASS_ALPHAKEY = 0x00000002,

    /* The input device is a touchscreen or a touchpad (either single-touch or multi-touch). */
    INPUT_DEVICE_CLASS_TOUCH = 0x00000004,

    /* The input device is a cursor device such as a trackball or mouse. */
    INPUT_DEVICE_CLASS_CURSOR = 0x00000008,

    /* The input device is a multi-touch touchscreen. */
    INPUT_DEVICE_CLASS_TOUCH_MT = 0x00000010,

    /* The input device is a directional pad (implies keyboard, has DPAD keys). */
    INPUT_DEVICE_CLASS_DPAD = 0x00000020,

    /* The input device is a gamepad (implies keyboard, has BUTTON keys). */
    INPUT_DEVICE_CLASS_GAMEPAD = 0x00000040,

    /* The input device has switches. */
    INPUT_DEVICE_CLASS_SWITCH = 0x00000080,

    /* The input device is a joystick (implies gamepad, has joystick absolute axes). */
    INPUT_DEVICE_CLASS_JOYSTICK = 0x00000100,

    /* The input device has a vibrator (supports FF_RUMBLE). */
    INPUT_DEVICE_CLASS_VIBRATOR = 0x00000200,

    /* The input device has a microphone. */
    INPUT_DEVICE_CLASS_MIC = 0x00000400,

    /* The input device is an external stylus (has data we want to fuse with touch data). */
    INPUT_DEVICE_CLASS_EXTERNAL_STYLUS = 0x00000800,

    /* The input device has a rotary encoder */
    INPUT_DEVICE_CLASS_ROTARY_ENCODER = 0x00001000,

    /* The input device is virtual (not a real device, not part of UI configuration). */
    INPUT_DEVICE_CLASS_VIRTUAL = 0x40000000,

    /* The input device is external (not built-in). */
    INPUT_DEVICE_CLASS_EXTERNAL = 0x80000000,
};

InputReader

InputReader.cpp
InputReader的主要功能就是不断地从EventHub中获取硬件消息,然后处理数据,最后分发到InputDispatcher,值得注意的是,InputReader运行在一个独立的线程中。

主要代码如下:


    // With each iteration of the loop, InputReader reads and processes one incoming message from
    // the EventHub.
void InputReader::loopOnce() {
    int32_t oldGeneration;
    int32_t timeoutMillis;
    bool inputDevicesChanged = false;
    std::vector<InputDeviceInfo> inputDevices;
    { // acquire lock
        AutoMutex _l(mLock);

        oldGeneration = mGeneration;
        timeoutMillis = -1;

        uint32_t changes = mConfigurationChangesToRefresh;
        if (changes) {
            mConfigurationChangesToRefresh = 0;
            timeoutMillis = 0;
            refreshConfigurationLocked(changes);
        } else if (mNextTimeout != LLONG_MAX) {
            nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
            timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout);
        }
    } // release lock

    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);

    { // acquire lock
        AutoMutex _l(mLock);
        mReaderIsAliveCondition.broadcast();

        if (count) {
            processEventsLocked(mEventBuffer, count);
        }

        if (mNextTimeout != LLONG_MAX) {
            nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
            if (now >= mNextTimeout) {
#if DEBUG_RAW_EVENTS
                ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f);
#endif
                mNextTimeout = LLONG_MAX;
                timeoutExpiredLocked(now);
            }
        }

        if (oldGeneration != mGeneration) {
            inputDevicesChanged = true;
            getInputDevicesLocked(inputDevices);
        }
    } // release lock

    // Send out a message that the describes the changed input devices.
    if (inputDevicesChanged) {
        mPolicy->notifyInputDevicesChanged(inputDevices);
    }

    // Flush queued events out to the listener.
    // This must happen outside of the lock because the listener could potentially call
    // back into the InputReader‘s methods, such as getScanCodeState, or become blocked
    // on another thread similarly waiting to acquire the InputReader lock thereby
    // resulting in a deadlock.  This situation is actually quite plausible because the
    // listener is actually the input dispatcher, which calls into the window manager,
    // which occasionally calls into the input reader.
    mQueuedListener->flush();
}


值得注意的是processEventsLocked(mEventBuffer, count)这一处调用,其深层调用过程如下:

graph TD; a(processEventsLocked) --> b(processEventsForDeviceLocked); b-->c(InputDevice::process); c-->d(InputMapperImpl::process); d-->e(Dispatcher::notifyXXX)

此处的InputMapperImpl并不是指某一具体类,而是Android系统针对上述所支持的设备所创建的实现类。

InputDispatcher

InputDispatcher同样运行在一个独立的线程中,其构造函数如下:

InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy)
      : mPolicy(policy),
        mPendingEvent(nullptr),
        mLastDropReason(DropReason::NOT_DROPPED),
        mIdGenerator(IdGenerator::Source::INPUT_DISPATCHER),
        mAppSwitchSawKeyDown(false),
        mAppSwitchDueTime(LONG_LONG_MAX),
        mNextUnblockedEvent(nullptr),
        mDispatchEnabled(false),
        mDispatchFrozen(false),
        mInputFilterEnabled(false),
        // mInTouchMode will be initialized by the WindowManager to the default device config.
        // To avoid leaking stack in case that call never comes, and for tests,
        // initialize it here anyways.
        mInTouchMode(true),
        mFocusedDisplayId(ADISPLAY_ID_DEFAULT) {
    mLooper = new Looper(false);
    mReporter = createInputReporter();

    mKeyRepeatState.lastKeyEntry = nullptr;

    policy->getDispatcherConfiguration(&mConfig);
}

可以看到其拥有一个Looper

前面说到InputReader通过notifyXXX 函数将事件分发到了InputDispatcher

notifyXXX主要做了四件事:

  1. 将分发过来的RawEvent包装为最终消费的KeyEvent
  2. KeyEvent打上标记,有一些KeyEvent并不需要交给应用响应,直接交给Framework,例如电源键;
  3. KeyEvent添加到队列
  4. 唤醒Looper,进行更深层次处理

队列处理调用过程如下:

graph TD; a(dispatchOnce) --> b(dispatchOnceInnerLocked); b-->c(dispatchKeyLocked); c-->d(dispatchEventLocked); d-->e(prepareDispatchCycleLocked); e-->f(enqueueDispatchEntriesLocked); f-->g(startDispatchCycleLocked); g-->h(InputPublisher::publishEvent);

InputPublisher::publishXXX函数会将参数包装成InputMessage,然后通过InputChannelInputMessage发送到Socket

ViewRootImpl

WindowInputEventReceiver.class

socket读取数据再次包装,放入Looper,通过Hanlder处理;

    private void deliverInputEvent(QueuedInputEvent q) {
        Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
                q.mEvent.getId());

        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent src=0x"
                    + Integer.toHexString(q.mEvent.getSource()) + " eventTimeNano="
                    + q.mEvent.getEventTimeNano() + " id=0x"
                    + Integer.toHexString(q.mEvent.getId()));
        }
        try {
            if (mInputEventConsistencyVerifier != null) {
                Trace.traceBegin(Trace.TRACE_TAG_VIEW, "verifyEventConsistency");
                try {
                    mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
                } finally {
                    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
                }
            }

            InputStage stage;
            if (q.shouldSendToSynthesizer()) {
                stage = mSyntheticInputStage;
            } else {
                stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
            }

            if (q.mEvent instanceof KeyEvent) {
                Trace.traceBegin(Trace.TRACE_TAG_VIEW, "preDispatchToUnhandledKeyManager");
                try {
                    mUnhandledKeyManager.preDispatch((KeyEvent) q.mEvent);
                } finally {
                    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
                }
            }

            if (stage != null) {
                handleWindowFocusChanged();
                stage.deliver(q);
            } else {
                finishInputEvent(q);
            }
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

最终交给了InputStage处理,InputStage功能如下:

    /**
     * Base class for implementing a stage in the chain of responsibility
     * for processing input events.
     * <p>
     * Events are delivered to the stage by the {@link #deliver} method.  The stage
     * then has the choice of finishing the event or forwarding it to the next stage.
     * </p>
     */
                // 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;

最终会分发到ViewPostImeInputState,具体处理如下:

        private int processPointerEvent(QueuedInputEvent q) {
            final MotionEvent event = (MotionEvent)q.mEvent;

            mAttachInfo.mUnbufferedDispatchRequested = false;
            mAttachInfo.mHandlingPointerEvent = true;
            boolean handled = mView.dispatchPointerEvent(event);
            maybeUpdatePointerIcon(event);
            maybeUpdateTooltip(event);
            mAttachInfo.mHandlingPointerEvent = false;
            if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
                mUnbufferedInputDispatch = true;
                if (mConsumeBatchedInputScheduled) {
                    scheduleConsumeBatchedInputImmediately();
                }
            }
            return handled ? FINISH_HANDLED : FORWARD;
        }

DecorView

上述代码中的mView实际上就是DecorView,它重写了View的方法;

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        final Window.Callback cb = mWindow.getCallback();
        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
    }

这里的cb就是Activity,具体代码如下:

    /**
     * Called to process touch screen events.  You can override this to
     * intercept all touch screen events before they are dispatched to the
     * window.  Be sure to call this implementation for touch screen events
     * that should be handled normally.
     *
     * @param ev The touch screen event.
     *
     * @return boolean Return true if this event was consumed.
     */    
public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

Window的具体调用如下:

    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

接下来就是传统的分析过程。

ViewGroup与View之间的事件传递和消费

ViewGroup向View传递事件的过程

ViewGroup中触摸事件详解 这篇文章详细讲述了触摸事件从ViewGroup分发到View的过程:

  1. 检查mViewFlags,当此ViewGroup被遮蔽且不被允许接收来自上层ViewGroup的事件时,则放弃分发触摸事件;
  2. 如果当前事件为ACTIOIN_DOWN,则清除所有的待分发对象,开始一轮新的事件分发,接下来的步骤3,4,5,6都属于此分支;
  3. 检查mGroupFlags,当允许此ViewGroup拦截事件时,调用onInterceptTouchEvent,获得intercepted的值,决定是否将此事件分发到子视图;
  4. 检查mPrivateFlags,获得canceled的值,表示此ViewGroup是否已被添加到视图;
  5. interceptedcanceled都为false时,开始向下分发事件;重置所有待分发对象的触摸ID;按照Z轴顺序获得子视图列表;
  6. 开始轮询子视图,如果子视图能够接收事件且触摸位置在子视图内,分发事件到子视图,并且添加到待分发列表;
  7. 轮询结束,此时获得待分发列表,如果列表为空,则调用ViewGroupOnTouchEvent函数;如果不为空,则开始向列表中的子视图分发其他事件;
  8. 再次检查取消标记,并进行相应的处理;

View消费事件的过程

  1. 如果当前事件为ACTION_DOWN,则停止滚动;
  2. 如果OnTouchListener分发成功,则不再分发;
  3. 如果OnTouchEvent分发成功,则不再分发;
  4. 检查事件标记,停止滚动;

onTouchEvent的过程

  1. 检查mViewFlags,如果当前View不可用,则直接返回clickable状态;
  2. 检查mTouchDelegate,如果mTouchDeletegate不为空,则将所有事件转发给此代理;
  3. 如果当前View可点击或可显示提示,则开始对触摸事件的具体处理,一共四种类型:ACTION_DOWNACTION_UPACTION_MOVEACTION_CANCEL
  4. ACTION_DOWN
    1. 如果不可点击,则向Handler中写入一个延迟500ms的Runnable,执行performLongClick,处理结束;
    2. 如果显示右键菜单,则处理结束;
    3. 如果当前View处于一个可滚动容器内,则向Handler中写入一个延迟100ms的Runnable;否则,向Handler中写入一个延迟500ms的Runnable;处理结束;
  5. ACTION_UP:
    1. 1500ms后隐藏ToolTip
    2. 如果不可点击,移除长按事件,重置按压状态,处理结束;
    3. 如果处于按压状态:
      1. 如果没有获取焦点,则获取焦点;
      2. 设置按压状态;
      3. 如果还未响应长按事件,则移除长按事件;如果向Handler写入PerformClick失败,则直接调用performClickInternal函数;
      4. 64ms后重置按压状态;
      5. 移除另一个长按事件;
  6. ACTION_MOVE:
    1. 如果可点击,则重新绘制水波纹;
    2. 如果当前事件为意图不明的手势且点击位置不在此视图内,则移除长按事件并且按照计算好的延迟重发长按事件;
    3. 如果点击位置不在此视图内,移除所有长按事件,并且重置按压状态;
    4. 如果事件类型为用力按压,移除长按事件并且立即重发;
  7. ACTION_CANCEL:
    1. 如果可点击,则重置按压状态;
    2. 移除所有长按事件;
    3. 重置一些flag

Android触摸事件分发

标签:delay   virt   ble   ram   last   mes   rect   false   The   

原文地址:https://www.cnblogs.com/ijkzen/p/14308813.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!