深入浅出Android中的事件分发机制


从输入硬件到Android窗口

这里直接从底层硬件分析到事件传递到View的流程,具体的调用链查看源码,这里是基于Android 10 的源码分析的.

输入事件

主要流程如下:

  1. 输入设备产生信号,比如手触摸屏幕,触摸屏产生电流或者电压信号,传递给设备固件
  2. 设备固件对信号进行编码和处理,比如产生中断,比如发送USB HID报告
  3. Linux内核驱动程序接收到信号,并按照input.h中定义的协议,将信号转换成事件
  4. EventHub通过libevdev读取各个设备(如/dev/input/event4)的事件,并传递给InputReader
  5. InputReader将事件封装成Android事件流,传递给InputDispatcher进行分发
  6. InputDispatcher通过InputChannel与Java层的InputEventReceiver联系上,并调用它的方法进行事件分发
  7. 最终会进入到ViewRootImpl中,根据输入阶段不同,进行不同的处理,最终会调用到DecorView的dispatchTouchEvent()方法
  8. 再之后就是我们熟悉的View/ViewGroup事件分发流程了

View/ViewGroup事件分发

  1. 进入到DecorView中的dispatchTouchEvent(event)方法:

        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            // 这里的callback实现类有Activity,Dialog等,这里只分析Activity的
            final Window.Callback cb = mWindow.getCallback();
            return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                    ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
        }
  2. Activity中:

        public boolean dispatchTouchEvent(MotionEvent ev) {
            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
                onUserInteraction();
            } 
            // 调用window的事件分发
            if (getWindow().superDispatchTouchEvent(ev)) {
                return true;
            }
            return onTouchEvent(ev);
        }
  3. Window的实现类是PhoneWindow中,因此:

        @Override
        public boolean superDispatchTouchEvent(MotionEvent event) {
            // mDecor是DecorView
            return mDecor.superDispatchTouchEvent(event);
        }
  4. DecorView中:

        public boolean superDispatchTouchEvent(MotionEvent event) {
            // ViewGroup
            return super.dispatchTouchEvent(event);
        }
  5. ViewGroup中:

        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            boolean handled = false;
            //如果window做了模糊处理,则不处理事件分发
            if (onFilterTouchEventForSecurity(ev)) {
                final int action = ev.getAction();
                final int actionMasked = action & MotionEvent.ACTION_MASK;
    
                // 重制状态
                if (actionMasked == MotionEvent.ACTION_DOWN) {
                    cancelAndClearTouchTargets(ev);
                    resetTouchState();
                }
    
                // 拦截处理
                final boolean intercepted;
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || mFirstTouchTarget != null) {
                    // View有方法可以设置
                    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                    if (!disallowIntercept) {
                        // 关键点: 拦截
                        intercepted = onInterceptTouchEvent(ev);
                        ev.setAction(action); // restore action in case it was changed
                    } else {
                        intercepted = false;
                    }
                } else {
                    // mFirstTouchTarget == null && action != ACTION_DOWN
                    intercepted = true;
                }
    
                // Check for cancelation.
                final boolean canceled = resetCancelNextUpFlag(this)
                        || actionMasked == MotionEvent.ACTION_CANCEL;
    
                // Update list of touch targets for pointer down, if needed.
                final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
                TouchTarget newTouchTarget = null;
                boolean alreadyDispatchedToNewTouchTarget = false;
                // 不是取消事件,也没有拦截
                if (!canceled && !intercepted) {
                    if (actionMasked == MotionEvent.ACTION_DOWN
                            || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                            || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
    
                        final int childrenCount = mChildrenCount;
                        if (newTouchTarget == null && childrenCount != 0) {
                            final float x = ev.getX(actionIndex);
                            final float y = ev.getY(actionIndex);
                            // 根据z重新排序子View
                            final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                            final boolean customOrder = preorderedList == null
                                    && isChildrenDrawingOrderEnabled();
                            final View[] children = mChildren;
                            for (int i = childrenCount - 1; i >= 0; i--) {
                                // 关键
                                if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                    // 为mFirstTouchTarget赋值
                                    newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                    alreadyDispatchedToNewTouchTarget = true;
                                    break;
                                }
                            }
                            if (preorderedList != null) preorderedList.clear();
                        }
                    }
                }
    
                // Dispatch to touch targets.
                if (mFirstTouchTarget == null) {
                   // 关键
                    handled = dispatchTransformedTouchEvent(ev, canceled, null,
                            TouchTarget.ALL_POINTER_IDS);
                } else {
    
                    TouchTarget predecessor = null;
                    TouchTarget target = mFirstTouchTarget;
                    while (target != null) {
                        final TouchTarget next = target.next;
                        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                            handled = true;
                        } else {
                            final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                    || intercepted;
                            // 关键
                            if (dispatchTransformedTouchEvent(ev, cancelChild,
                                    target.child, target.pointerIdBits)) {
                                handled = true;
                            }
                        }
    
                    }
                }
            }
    
            return handled;
        }

    这里会进行一些判断(比如触摸点是否在对应的View内,避免无效计算),然后:

        private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                View child, int desiredPointerIdBits) {
            final boolean handled;
            final int oldAction = event.getAction();
            if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
                event.setAction(MotionEvent.ACTION_CANCEL);
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    handled = child.dispatchTouchEvent(event);
                }
                event.setAction(oldAction);
                return handled;
            }
    
            final MotionEvent transformedEvent;
            if (newPointerIdBits == oldPointerIdBits) {
                if (child == null || child.hasIdentityMatrix()) {
                    if (child == null) {
                        handled = super.dispatchTouchEvent(event);
                    } else {
                        final float offsetX = mScrollX - child.mLeft;
                        final float offsetY = mScrollY - child.mTop;
                        event.offsetLocation(offsetX, offsetY);
                        // 关键
                        handled = child.dispatchTouchEvent(event);
    
                        event.offsetLocation(-offsetX, -offsetY);
                    }
                    return handled;
                }
                transformedEvent = MotionEvent.obtain(event);
            } else {
                transformedEvent = event.split(newPointerIdBits);
            }
    
            // Perform any necessary transformations and dispatch.
            if (child == null) {
                // 关键
                handled = super.dispatchTouchEvent(transformedEvent);
            } else {
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;
                transformedEvent.offsetLocation(offsetX, offsetY);
                if (! child.hasIdentityMatrix()) {
                    transformedEvent.transform(child.getInverseMatrix());
                }
                //关键
                handled = child.dispatchTouchEvent(transformedEvent);
            }
    
            // Done.
            transformedEvent.recycle();
            return handled;
        }

    这里就将事件分发给子View了.

  6. View#dispatchTouchEvent():

        public boolean dispatchTouchEvent(MotionEvent event) {
            boolean result = false;
            if (onFilterTouchEventForSecurity(event)) {
                if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                    result = true;
                }
                // 关键
                ListenerInfo li = mListenerInfo;
                if (li != null && li.mOnTouchListener != null
                        && (mViewFlags & ENABLED_MASK) == ENABLED
                        // OnTouchListener回调
                        && li.mOnTouchListener.onTouch(this, event)) {
                    result = true;
                }
                // 如果OnTouchListener消耗了事件,则不会执行后面的onTouchEvent()
                if (!result && onTouchEvent(event)) {
                    result = true;
                }
            }
            return result;
        }

    这里可以看到OnTouchListener#onTouch() 优先级 高于 onTouchEvent().

        public boolean onTouchEvent(MotionEvent event) {
            final float x = event.getX();
            final float y = event.getY();
            final int viewFlags = mViewFlags;
            final int action = event.getAction();
    
            final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
    
            // 关键
            if (mTouchDelegate != null) {
                if (mTouchDelegate.onTouchEvent(event)) {
                    return true;
                }
            }
    
            if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
                switch (action) {
                    case MotionEvent.ACTION_UP:
                        boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                            boolean focusTaken = false;
                            if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                                focusTaken = requestFocus();
                            }
                            if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                                // This is a tap, so remove the longpress check
                                removeLongPressCallback();
    
                                // 处理点击事件
                                if (!focusTaken) {
                                    if (mPerformClick == null) {
                                        // 一个Runnbale
                                        mPerformClick = new PerformClick();
                                    }
                                    if (!post(mPerformClick)) {
                                        //一般不会进入这个分支
                                        performClickInternal();
                                    }
                                }
                            }
                            removeTapCallback();
                        }
                        mIgnoreNextUpEvent = false;
                        break;
    
                    case MotionEvent.ACTION_DOWN:
                        mHasPerformedLongPress = false;
    
                        if (!clickable) {
                            // 处理长按点击事件
                            checkForLongClick(
                                    ViewConfiguration.getLongPressTimeout(),
                                    x,
                                    y,
                                    TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                            break;
                        }
                        if (isInScrollingContainer) {
                            mPrivateFlags |= PFLAG_PREPRESSED;
                            // 检查点击事件
                            if (mPendingCheckForTap == null) {
                                mPendingCheckForTap = new CheckForTap();
                            }
                            mPendingCheckForTap.x = event.getX();
                            mPendingCheckForTap.y = event.getY();
                            postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                        } else {
                            // 长按
                            setPressed(true, x, y);
                            checkForLongClick(
                                    ViewConfiguration.getLongPressTimeout(),
                                    x,
                                    y,
                                    TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                        }
                        break;
    
                    case MotionEvent.ACTION_CANCEL:
                        removeTapCallback();
                        removeLongPressCallback();
                        mInContextButtonPress = false;
                        mHasPerformedLongPress = false;
                        mIgnoreNextUpEvent = false;
                        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                        break;
    
                    case MotionEvent.ACTION_MOVE:
                        if (ambiguousGesture && hasPendingLongPressCallback()) {
                            if (!pointInView(x, y, touchSlop)) {
                                removeLongPressCallback();
                                long delay = (long) (ViewConfiguration.getLongPressTimeout()
                                        * mAmbiguousGestureMultiplier);
                                // Subtract the time already spent
                                delay -= event.getEventTime() - event.getDownTime();
                                checkForLongClick(
                                        delay,
                                        x,
                                        y,
                                        TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                            }
                            touchSlop *= mAmbiguousGestureMultiplier;
                        }
                        if (!pointInView(x, y, touchSlop)) {
                            // Outside button
                            // Remove any future long press/tap checks
                            removeTapCallback();
                            removeLongPressCallback();
                            if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                                setPressed(false);
                            }
                            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                        }
    
                        final boolean deepPress =
                                motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
                        if (deepPress && hasPendingLongPressCallback()) {
                            // process the long click action immediately
                            removeLongPressCallback();
                            checkForLongClick(
                                    0 /* send immediately */,
                                    x,
                                    y,
                                    TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
                        }
    
                        break;
                }
    
                return true;
            }
    
            return false;
        }

    View#onTouchEvent()中的逻辑也比较复杂,需要注意几个要点:

    • TouchDelegate具有很高的优先级,会最先进行判断,它是用来拓展View的可点击区域的(适合于View尺寸有限,但是需要扩大点击区域的场景)
    • 点击事件和长安事件目前都封装成了Runnable,在DOWN,MOVE,UP,CANCEL等几种事件里都有逻辑,本质就是通过view的延时post方法,即Handler的延时发送来进行调度的

基本流程如下:

  1. DecorView -> Activity -> Window ->ViewGroup#dispatchTouchEvent()
  2. ViewGroup#onInterceptTouchEvent(ev),返回true,表示拦截,否则表示不拦截
  3. 如果不拦截,则调用dispatchTransformedTouchEvent(),child为null,则调用super.dispatchTouchEvent(),super是什么,是View,因此把当前ViewGroup当作View,并执行它的dispatchTouchEvent()方法;如果child不为null,则直接执行child的dispatchTouchEvent()方法,进入循环,遇到没有子View的时候执行View的dispatchTouchEvent()方法;
  4. 如果拦截,则调用自己的dispatchTransformedTouchEvent(),传入的child为null,因此会执行super.dispatchTouchEvent(),把自己当作View,执行View的dispatchTouchEvent()方法;
  5. View#dispatchTouchEvent()方法中会执行onTouchEvent()方法;

到这里整个流程其实就走通了,唯一可能导致疑惑的地方其实就是ViewGroup如何调用自身的onTouchEvent(),其实这里记住一点即可:

ViewGroup的super就是View,View中才有默认实现的onTouchEvent().

优先级问题

从上面的源码分析可以看到:

OnTouchListener#onTouch() 大于 View#onTouchEvent() 大于 OnClickListener#onClick()

View#onTouchEvent()返回值问题

View如果消费了ACTION_DOWN,则后续的一系列ACTION都会传给这个View的onTouchEvent()来处理; View如果不消费ACTION_DOWN,则后续的一系列ACTION都不会交给它的onTouchEvent()处理;如果当前View和它的兄弟View都无法消费ACTION_DOWN,则会找它的父布局的onTouchEvent()去消费

所谓消费,指的就是View#onTouchEvent()的返回值,返回true,表示消费了,否则表示没消费

ViewGroup#onInterceptTouchEvent(ev)的问题

final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                intercepted = true;
            }

从dispatchTouchEvent()开始,一层层的调用,直到View#onTouchEvent(),返回值其实是ViewGroup#dispatchTouchEvent()最终使用的:

如果返回fasle,则不会为mFirstTouchTarget赋值,否则将child赋值给mFristTouchTarget.那这又影响了什么呢?

如果不调用API请求不拦截事件,则只有两种情况下的任意一种,才会触发拦截:

  • 当前事件是ACTION_DOWN
  • mFirstTouchTarget != null (当前ViewGroup的所有子View的onTouchEvent()均返回false)

因此得出结论:

  • 只有ViewGroup才有onInterceptTouchEvent(ev)方法
  • 只要是ACTION_DOWN事件,ViewGroup都会执行onInterceptTouchEvent(ev)方法
  • 如果不是ACTION_DOWN事件,则要判断该ViewGroup的所有子View中是否能消耗ACTION_DOWN事件,即子View中是否有onTouchEvent()返回为true,如果有,则还会执行onInterceptTouchEvent(ev)方法,如果一个都没有,则不会执行onInterceptTouchEvent(ev)方法
  • 如果ViewGroup的onInterceptTouchEvent(ev)返回了true,则其子View一种事件都获取不到,因为不会执行子View的dispatchTouchEvent()方法,就算仅仅是拦截了ACTION_DOWN;如果不拦截ACTION_DOWN,而拦截ACTION_MOVE,则子View可以正常捕获到ACTION_DOWN,但是后面会主动执行一个ACTION_CANCEL,就不再接收其他事件了.

adb查看工具

  • getevent : 输出所有event设备的基本信息

    130|cmi:/dev/input $ getevent
    add device 1: /dev/input/event7
      name:     "kona-mtp-snd-card USB_3_5 Jack"   #声音
    add device 2: /dev/input/event6
      name:     "kona-mtp-snd-card Button Jack"    #
    add device 3: /dev/input/event5
      name:     "kona-mtp-snd-card Headset Jack"  #耳机
    add device 4: /dev/input/event4
      name:     "fts"                             #触摸屏
    add device 5: /dev/input/event1
      name:     "uinput-goodix"                   #指纹传感器
    add device 6: /dev/input/event3               # 音量上按键
      name:     "gpio-keys"
    add device 7: /dev/input/event0               #高通按键驱动
      name:     "qpnp_pon"
    add device 8: /dev/input/event2
      name:     "aw8697_haptic"                  #电机驱动

    此时如果触摸屏幕或者按键,会输出:

    /dev/input/event4: 0003 0039 ffffffff

    格式为: 设备名: 事件type 事件code 事件value (这里的数字是16进制的)

    如果使用getevent -l,则会输出具体的常量名称,比较易于理解:

    /dev/input/event4: EV_SYN       SYN_REPORT           00000000

    这些常量是定义在sysroot/usr/include/linux/input.h中的.

  • sendevent: 模拟发送事件 (可能需要权限)

    格式为:

    sendevent DEVICE TYPE CODE VALUE

    如:

    sendevent /dev/input/event0 0001 0114 00000001

    这里面的值是由16进制转换而成的10进制数.

  • getevent -p: 查看详细的交互设备信息

    输出如下:

    1|cmi:/dev/input $ getevent -p
    add device 1: /dev/input/event7
      name:     "kona-mtp-snd-card USB_3_5 Jack"
      events:
        SW  (0005): 0002  0004  0013
      input props:
        
    add device 2: /dev/input/event6
      name:     "kona-mtp-snd-card Button Jack"
      events:
        KEY (0001): 00e2  0101  0102  0103  0104  0105
      input props:
        
    add device 3: /dev/input/event5
      name:     "kona-mtp-snd-card Headset Jack"
      events:
        SW  (0005): 0002  0004  0006  0007  0010  0011  0012  0013
      input props:
        
    add device 4: /dev/input/event4
      name:     "fts"
      events:
        KEY (0001): 0011  0012  0018  001a  001b  001f  0021  0026
                    002c  002e  002f  0032  003b  003c  003d  003e
                    003f  0067  0069  006a  006c  008f  0096  0145
                    014a  0152  0162
        ABS (0003): 002f  : value 0, min 0, max 9, fuzz 0, flat 0, resolution 0
                    0030  : value 0, min 0, max 1080, fuzz 0, flat 0, resolution 0
                    0031  : value 0, min 0, max 2340, fuzz 0, flat 0, resolution 0
                    0032  : value 0, min 0, max 127, fuzz 0, flat 0, resolution 0
                    0033  : value 0, min 0, max 127, fuzz 0, flat 0, resolution 0
                    0034  : value 0, min -90, max 90, fuzz 0, flat 0, resolution 0
                    0035  : value 0, min 0, max 1079, fuzz 0, flat 0, resolution 0
                    0036  : value 0, min 0, max 2339, fuzz 0, flat 0, resolution 0
                    0039  : value 0, min 0, max 65535, fuzz 0, flat 0, resolution 0
                    003b  : value 0, min 0, max 127, fuzz 0, flat 0, resolution 0
      input props:
        INPUT_PROP_DIRECT
    add device 5: /dev/input/event1
      name:     "uinput-goodix"
      events:
        KEY (0001): 0066  0067  0069  006a  006c  0072  0073  0074
                    008b  009e  00d4  00d8  00d9
      input props:
        
    add device 6: /dev/input/event3
      name:     "gpio-keys"
      events:
        KEY (0001): 0073
        SW  (0005): 0000
      input props:
        
    add device 7: /dev/input/event0
      name:     "qpnp_pon"
      events:
        KEY (0001): 0072  0074
      input props:
        
    add device 8: /dev/input/event2
      name:     "aw8697_haptic"
      events:
        FF  (0015): 0050  0051  0052  005d  0060
      input props:
        
  • getevent -lp或者 getevent -lp /dev/input/event4 : 输出交互设备信息文字标签信息

    add device 1: /dev/input/event4
      name:     "fts"
      events:
        KEY (0001): KEY_W                 KEY_E                 KEY_O                 KEY_LEFTBRACE
                    KEY_RIGHTBRACE        KEY_S                 KEY_F                 KEY_L
                    KEY_Z                 KEY_C                 KEY_V                 KEY_M
                    KEY_F1                KEY_F2                KEY_F3                KEY_F4
                    KEY_F5                KEY_UP                KEY_LEFT              KEY_RIGHT
                    KEY_DOWN              KEY_WAKEUP            KEY_WWW               BTN_TOOL_FINGER
                    BTN_TOUCH             0152                  KEY_GOTO
        ABS (0003): ABS_MT_SLOT           : value 0, min 0, max 9, fuzz 0, flat 0, resolution 0
                    ABS_MT_TOUCH_MAJOR    : value 0, min 0, max 1080, fuzz 0, flat 0, resolution 0
                    ABS_MT_TOUCH_MINOR    : value 0, min 0, max 2340, fuzz 0, flat 0, resolution 0
                    ABS_MT_WIDTH_MAJOR    : value 0, min 0, max 127, fuzz 0, flat 0, resolution 0
                    ABS_MT_WIDTH_MINOR    : value 0, min 0, max 127, fuzz 0, flat 0, resolution 0
                    ABS_MT_ORIENTATION    : value 0, min -90, max 90, fuzz 0, flat 0, resolution 0
                    ABS_MT_POSITION_X     : value 0, min 0, max 1079, fuzz 0, flat 0, resolution 0
                    ABS_MT_POSITION_Y     : value 0, min 0, max 2339, fuzz 0, flat 0, resolution 0
                    ABS_MT_TRACKING_ID    : value 0, min 0, max 65535, fuzz 0, flat 0, resolution 0
                    ABS_MT_DISTANCE       : value 0, min 0, max 127, fuzz 0, flat 0, resolution 0
      input props:
        INPUT_PROP_DIRECT
  • getevent -i: 显示更加详细的信息

  • getevent -t: 显示时间戳

参考文件

  1. https://source.android.com/devices/input

文章作者: 姜康
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 姜康 !
评论
 上一篇
Kotlin中的infix Kotlin中的infix
infix即 “中缀表示法”,比如 1 + 2表示操作符以中缀的形式处于操作数的中间.(对应的也有前缀表达式,后缀表达式) 在kotlin中有这样的几种调用: val map = mapOf<String,Int>(
2020-10-14
下一篇 
浅谈Android中的Broadcast 浅谈Android中的Broadcast
![Android 广播](https://oss.jiangkang.tech/jk/Android 广播.png) 广播实现机制先思考几个问题: 广播注册到了什么地方? 当发送广播的时候,是谁在分发广播? BroadcastRecei
2020-10-09
  目录