type
status
date
slug
summary
tags
category
icon
password

从输入硬件到Android窗口

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

View/ViewGroup事件分发

  1. 进入到DecorView中的dispatchTouchEvent(event)方法:
    1. @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); }
  1. Activity中:
    1. public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } // 调用window的事件分发 if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }
  1. Window的实现类是PhoneWindow中,因此:
    1. @Override public boolean superDispatchTouchEvent(MotionEvent event) { // mDecor是DecorView return mDecor.superDispatchTouchEvent(event); }
  1. DecorView中:
    1. public boolean superDispatchTouchEvent(MotionEvent event) { // ViewGroup return super.dispatchTouchEvent(event); }
  1. ViewGroup中:
    1. @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了.
  1. View#dispatchTouchEvent():
    1. 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()
  1. ViewGroup#onInterceptTouchEvent(ev),返回true,表示拦截,否则表示不拦截
  1. 如果不拦截,则调用dispatchTransformedTouchEvent(),child为null,则调用super.dispatchTouchEvent(),super是什么,是View,因此把当前ViewGroup当作View,并执行它的dispatchTouchEvent()方法;如果child不为null,则直接执行child的dispatchTouchEvent()方法,进入循环,遇到没有子View的时候执行View的dispatchTouchEvent()方法;
  1. 如果拦截,则调用自己的dispatchTransformedTouchEvent(),传入的child为null,因此会执行super.dispatchTouchEvent(),把自己当作View,执行View的dispatchTouchEvent()方法;
  1. 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: <none> add device 2: /dev/input/event6 name: "kona-mtp-snd-card Button Jack" events: KEY (0001): 00e2 0101 0102 0103 0104 0105 input props: <none> 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: <none> 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: <none> add device 6: /dev/input/event3 name: "gpio-keys" events: KEY (0001): 0073 SW (0005): 0000 input props: <none> add device 7: /dev/input/event0 name: "qpnp_pon" events: KEY (0001): 0072 0074 input props: <none> add device 8: /dev/input/event2 name: "aw8697_haptic" events: FF (0015): 0050 0051 0052 005d 0060 input props: <none>
  • 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
使用VSCode阅读Android源码Android中的Handler
姜康
姜康
一个软件工程师
公告
type
status
date
slug
summary
tags
category
icon
password
🎉博客网站重新制作了🎉
👏欢迎更新体验👏