Android-触摸事件

Android-触摸事件

1. 读取触摸事件

Android 触摸事件属于输入事件的一种,因此也是通过输入事件读取的。

SystemService 进程启动后,在 startOtherServices() 中几乎同时启动了 InputManagerService 和 WindowManagerService:

1
2
3
4
5
6
7
8
9
10
11
private void startOtherServices() {
......
inputManager = new InputManagerService(context);
// WMS 持有了 IMS:
wm = WindowManagerService.main(context, inputManager,
mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
!mFirstBoot, mOnlyCore);
ServiceManager.addService(Context.WINDOW_SERVICE, wm);
ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
...
}

2. 分发触摸事件

触摸事件机制主要分为两个步骤:事件读取、事件分发;其中事件分发又可以按照「从 IMS 分发到 Window」和「从 Window 分发到 View」两个流程。

2.1 IMS分发至Window

2.2 Window分发至View

IMS 在启动后会创建两个线程,一个负责不断读取、一个负责不断分发:

  • 读取线程 InputReaderThread 通过基于 Linux 内核 inotify 和 epoll 机制的 EventHub 监听设备的插拔、触摸、按钮等事件,主要面向的是 dev/input/ 下的设备节点,例如 dev/input/event0 对应的就是输入事件,EventHub 调用 getEvents() 获取各种输入事件,然后调用 processEventLocked() 将输入事件封装成 RawEvent,然后向分发线程发送一条 Message。
  • 分发线程 InputDispatcher 是典型的 Looper 线程,基于 Native 的 Handler 机制阻塞等待事件,当 InputReaderThread 调用 InputDispatcher 对象的 mQueuedListener.flush() 后,InputDispatcher 就调用 dispatchOnceInnerLocked() 进行事件分发。

每当添加一个 Window 时都会调用 WindowManagerImpl#addView(...),会计算窗口的大小以及 Z-Order (type) 属性,最终通过 WMS 记录到 SurfaceFlinger 中,SF 中 WindowList 记录了所有 Window 的信息,包括 Window 的显示范围、颜色格式、对应的实例等,因此当 InputDispatcher 分发事件时,首先调用 findTouchedWindowTargetsLocked() 请求 SF 查找对应的 Window,SF 遍历 WindowList 判断 Window 的范围、Z-Order 等找到目标 Window,然后通过 Socket 把对应的输入事件发送到应用进程的目标 Window 的 ViewRootImpl,ViewRootImpl 在 setView(...) 中调用 mWindowSession.addToDisplay(...) 创建了一个 Socket 连接并开启了 Input 通道 mInputChannel,然后把这个 Socket 添加到 Native 层 Looper 的 epoll 数组中,这样只要收到 IMS 发来的事件就会唤醒 Native Looper,然后在封装成 Java 层对象 InputEvent,然后回调到 Java 层 ViewRootImpl 内部类 ViewPostImeInputStage#processPointerEvent(QueuedInputEvent),ViewRootImpl 调用 DecorView 重写的 dispatchTouchEvent(...) 方法,DecorView 优先将 TouchEvent 回调给注册的 WindowCallback,PhoneWindow 和 Activity 都注册了 WindowCallback,因此 TouchEvent 会优先派发给 PhoneWindow 和 Activity,如果它们不处理,则会调用 mDecor.superDispatchTouchEvent(event); 把触摸事件再回调到 DecorView,然后 DecorView 继续调用 super.dispatchTouchEvent(event); 回到 ViewGroup#dispatchTouchEvent(MotionEvent),ViewRootImpl 没有重写这个方法,所以就会按照 ViewTree 的层级,再从 DecorView 的 onDispatchEvent() 开始逐层向下分发。

Android 的触摸事件有两个方向,一个是向下分发(子控件),一个是向上传递(父控件),onInterceptTouchEvent 表示是否拦截向下分发,返回 true 则子控件不会响应触摸事件,返回 false 则子控件的 onInterceptTouchEvent 被调用。onTouchEvent 表示是否处理事件,返回 true 则表示触摸事件在该 View 处理,不再向上传递。通常,ViewGroup 的 onInterceptTouchEvent 返回 false,onTouchEvent 返回 false,子 View 如果不是 ViewGroup,则没有 onInterceptTouchEvent 方法,onTouchEvent 返回 true,需要处理事件的子 View 如果在 ACTION_DOWN 时 onTouchEvent 不返回 true,表示没有消费 ACTION_DOWN,则将无法响应 ACTION_MOVE 和 ACTION_UP。

  • ViewRootImpl接受事件吗

接收。事件的传递顺序:硬件(屏幕)、ViewRootImpl、DecorView、PhoneWindow、Activity。


参考文献