Android - 事件分发

2020-06-17  本文已影响0人  simplehych

本文从以下方面分析 Android 的事件分发,旨在梳理和理解,并没有深度展开

  1. 什么是事件
  2. 事件的分类
  3. 事件的种类
  4. 一次 Touch 事件流的序列
  5. Touch 事件的传递层级
  6. Touch 事件的分发
  7. Touch 事件的冲突解决
  8. 如何确定哪个 View 处理事件
  9. 事件的来源
  10. 渲染

部分内容为自己的理解,如果错误还望指正

1. 什么是事件

与安卓设备进行的一次交互,不同的安卓设备交互方式不一样。

如:
按键:Home 键,Back 键,音量键
触摸屏幕:手指在屏幕上 按下 / 滑动 / 抬起
轨迹球:例如黑莓手机存在轨迹球

在 Android 中统称为 输入事件,API 体现在 InputEvent

2. 事件分类

Android 中的输入事件 InputEvent 子类有两个:

  1. KeyEvent:按键 key、按钮 button 事件
  2. MotionEvent:鼠标 mouse、画笔 pen、手指 finger、轨迹球 trackball 等运动事件

不同类型的安卓设备,API 体现在 InputDevice

  1. SOURCE_CLASS_POINTER:指示设备,eg:触摸屏幕 SOURCE_TOUCHSCREEN 或鼠标SOURCE_MOUSE
  2. SOURCE_CLASS_TRACKBALL:轨迹球
  3. SOURCE_CLASS_JOYSTICK:摇杆
  4. ...

3. 事件的种类

KeyEvent 常见事件有:

  1. KEYCODE_HOME
  2. KEYCODE_MENU
  3. KEYCODE_BACK
  4. KEYCODE_MEDIA_PLAY / KEYCODE_MEDIA_PAUSE
  5. ...

获取 KeyEvent 方式:KeyEvent#getKeyCode()

MotionEvent 常见的事件有:

  1. ACTION_DOWN / ACTION_UP / ACTION_MOVE
  2. ACTION_CANCEL
  3. ACTION_POINTER_DOWN / ACTION_POINTER_UP
  4. ...
  5. ACTION_HOVER_MOVE / ACTION_HOVER_ENTER / ACTION_HOVER_EXIT / ACTION_SCROLL,不属于 TouchEvent

获取 MotionEvent 方式:MotionEvent#getAction()/getActionMask()

touch event 的界定:MotionEvent#isTouchEvent
MotionEvent 所有的事件去除上面的 4 个事件,因为没有真正的按下操作,这 4 个事件传递通过 View#onGenericMotionEvent(MotionEvent) 而不是 View#onTouchEvent(MotionEvent)

本文主要讨论 TouchEvent 的传递分发,属于 MotionEvent,没有 API 的体现

ViewRootImpl.ViewPostImeInputStage#processPointerEvent(QueuedInputEvent)
  ViewRootImpl#processPointerEvent(QueuedInputEvent)
    View#dispatchPointerEvent(MotionEvnet)
      View#dispatchTouchEvent(MotionEvent)

4. 一次 Touch 事件流的序列

序列 1:ACTION_DOWN -> ACTION_UP
序列 2:ACTION_DOWN -> ACTION_MOVE...ACTION_MOVE -> ACTION_UP
序列 3:ACTION_DOWN -> ACTION_MOVE...ACTION_MOVE -> ACTION_CANCEL, 非人为的结束

每个事件流都以 ACTION_DOWN 开始

如果前面的事件 dispatchTouchEvent 返回 false,后面一系列事件都不会执行

5. Touch 事件传递层级

Activity -> PhoneWindow -> DecorView -> ViewGroup -> View

从最下层开始向上层传递,当上层 View 响应事件后,下层 View 将不会再响应

6. Touch 事件的分发

注意以下方法的优先级及返回值

6.1 View

dispatchTouchEvent
mTouchListener.onTouch
onTouchEvent
mOnLongClickListener.onLongClick
onClickListener.onClick

6.2 ViewGroup

dispatchTouchEvent
cancelAndClearTouchTargets()
onInterceptTouchEvent
disallowInterceptTouchEvent
dispatchTransformedTouchEvent()
addTouchTarget

6.3 Activity

dispatchTouchEvent()
onUserInteraction()
onTouchEvent

7. Touch 事件的冲突解决

7.1 ViewGroup 如何拦截

父 ViewGroup 中重写 onInterceptTouchEvent() 默认返回 false 不拦截

在触发事件 MotionEvent.ACTION_DOWN 时选择拦截返回 true,则该事件不会往子 View 传递

DOWN 事件返回 true,则子 View 不会捕获到 DOWN、MOVE、UP 等事件
MOVE 事件返回 true,则子 View 不会捕获到 MOVE、UP 等事件

7.2 View 如何不被拦截

子 View 中调用 getParent().requestDisallowInterceptTouchEvent(true)

即使 ViewGroup 在 MOVE 的时候拦截了,子 View 依然能捕获到 MOVE、UP 等事件
但是,如果 ViewGroup 在 DOWN 的时候就拦截了,子 View 无法捕获到任何事件!!因为 DOWN 会重置

8. 如何确定哪个 View 处理事件

FirstTouchTarget 是一个链表,保存着事件处理的 View

ViewGroup#addTouchTarget

通过 dispatchTouchEvent 的返回值 true 确定

9. 事件的来源

Android 输入事件的源头是 /dev/input/ 下的设备节点,然后由 WMS 管理窗口,最终由 View 处理。

最初的输入事件为内核生成的原始事件,而交付给窗口的则是 KeyEventMotionEvent 对象。

输入事件由 Native层 进入到 Java层 的第一个函数是 InputEventReceiver.dispatchInputEvent()

-> ActivityThread#handleLaunchActivity 
-> ActivityThread#performLaunchActivity 
-> ActivityThread#createBaseContextForActivity
     -> Instrumentation#newActivity()  //反射创建 Activity
            -> Activity#attach
                  -> new PhoneWindow() & window.setCallback(this) // 包含事件分发的回调
            -> Activity#setTheme 
     -> Instrumentation#callActivityOnCreate 
            -> Activity#setContentView
                  -> PhoneWindow#setContentView 
                        -> DecorView#installDecor
                        -> DecorView#generateDecor() & generateLayout()
-> ActivityThread#handleResumeActivity 
    -> WindowManagerImpl#addView(decor, LayoutParams)
        -> WindowManagerGlobal#addView
              -> new ViewRootImpl() //构造函数
              -> ViewRootImpl#setView(decor) // 将 window 和 DecorView 关联
                    -> IWindowSession#addToDisplay(mWindow,mInputChannel) // InputChannel IPC fd
                        -> WindowManagerService#addWindow()
                              -> WindowState#openInputChannel(InputChannel)
                                 -> InputManagerService#registerInputChannel(inputChannel) // native
              -> new WindowInputEventReceiver(mInputChannel)
                                       -> InputEventReceiver#dispatchInputEvent(InputEvent) // Called from native code. 
              -> ViewRootImpl.WindowInputEventReceiver#onInputEvent(InputEvent)
                                            -> mChoreographer.mFrameInfo.updateInputEventTime(eventTime, oldestEventTime);
              -> ViewRootImpl.InputStage#deliver(QueuedInputEvent)
              -> ViewRootImpl.ViewPostImeInputStage.onProcess()
              -> ViewRootImpl#processPointerEvent 
                -> DecorView.dispatchPointerEvent(MotionEvent) 
                  -> mWindow.getCallback().dispatchTouchEvent(ev) // Callback为 Activity 的回调
                    -> Activity.dispatchTouchEvent(ev) 
                      -> window.superDispatchTouchEvent(ev) 
                        -> mDecor.superDispatchTouchEvent(ev)

事件的传递 java 层通过 WMS 和 ViewRootImpl 利用 Callback 回调来完成,然后 Choreographer 进行渲染

10. 渲染 Choreographer

Choreographer 顺序处理 input、animation、drawing 三个 ui 操作
Choreographer 接收显示系统的时间脉冲(垂直脉冲信号 VSync 信号),在下一个 frame 渲染时控制执行这些操作
注意卡顿造成丢帧的情况

参考资料

Android Touch事件分发超详细解析(附源码)
Android View 事件分发机制 来源
Android 点击事件的来源
Android Choreographer 源码分析
一文读懂Android View事件分发机制
hencoder 触摸反馈

上一篇下一篇

猜你喜欢

热点阅读