Android 7系统输入(一):从硬件到应用的事件旅程

系列目录第一篇:从硬件到应用的事件旅程 | 第二篇:EventHub | 第三篇:InputReader | 第四篇:InputDispatcher | 第五篇:应用侧


一、为什么要研究输入系统

做过 Android 开发的朋友应该都跟 onTouchEvent()dispatchTouchEvent() 打过交道。滑动冲突、点击穿透、ANR 弹窗......这些日常问题背后,都藏着一个庞大而精密的事件分发体系。

但如果你只停留在应用层的事件分发机制,很多深层问题会无从下手:

  • 为什么有时候 onTouchEvent 迟迟收不到事件?是 InputDispatcher 卡了还是主线程消息队列满了?
  • "应用无响应"弹窗背后的输入超时机制到底是怎么判定的?
  • 插上一个外接键盘,Android 怎么做到"即插即用"地开始接收按键?
  • 一个触摸事件从手指按下到 View.onTouchEvent() 被调用,中间到底经过了多少层?

这些问题,只有把 Android 输入系统的全链路搞清楚,才能彻底理解。本系列将以 AOSP 7(android-7.1.2_r36) 为基准源码版本,带你从内核驱动一路走到应用层 View 树,逐层拆解 Android 输入子系统。

本篇是整个系列的总览与导航,先帮你建立起完整的宏观认知。


二、宏观架构:一张图看懂事件流水线

在 Android 输入系统中,一个事件的生命周期可以用一条清晰的流水线来描述:

复制代码
硬件驱动 --> Linux Kernel --> EventHub --> InputReader
(触摸屏)      /dev/input/      (Native)     (Native)
                                                  |
                                             NotifyArgs
                                                  |
                                                  v
   APP进程  <-- InputDispatcher <-- InputManager
 ViewRootImpl   (socket/Channel)    Service(Java)

或者用更直观的分层视角来看:

层级 组件 进程 职责
内核层 Linux Input 子系统 Kernel 驱动硬件,向 /dev/input/eventX 写入 input_event
Native 采集层 EventHub system_server 监听设备热插拔,读取原始事件
Native 加工层 InputReader system_server 将原始数据转换为 KeyEvent / MotionEvent
Native 分发层 InputDispatcher system_server 确定目标窗口,通过 socket 发送事件,监控 ANR
Java 管理层 InputManagerService system_server 与 WMS 联动,管理输入法、手势等策略
应用接收层 ViewRootImpl APP 进程 从 socket 接收事件,沿 View 树分发

三、内核层:Linux Input 子系统的角色

3.1 /dev/input/ 设备节点

当你插上一个 USB 键盘或者设备自带触摸屏时,Linux 内核会在 /dev/input/ 目录下创建对应的设备节点:

bash 复制代码
$ ls /dev/input/
event0  event1  event2  event3  event4  mice  mouse0

每个 eventX 都对应一个输入设备。可以用 getevent 命令实时查看原始事件:

bash 复制代码
$ adb shell getevent -lt /dev/input/event2
# 触摸屏按下
[  123456.789] EV_ABS       ABS_MT_TRACKING_ID   00000045
[  123456.789] EV_ABS       ABS_MT_POSITION_X    00000320
[  123456.789] EV_ABS       ABS_MT_POSITION_Y    00000580
[  123456.789] EV_SYN       SYN_REPORT           00000000
# 触摸屏抬起
[  123457.123] EV_ABS       ABS_MT_TRACKING_ID   ffffffff
[  123457.123] EV_SYN       SYN_REPORT           00000000

3.2 input_event 结构体

内核中所有输入事件都用一个统一的结构体来描述:

源码路径kernel/include/linux/input.h

c 复制代码
struct input_event {
    struct timeval time;   // 时间戳
    __u16 type;            // 事件类型(EV_KEY、EV_ABS、EV_SYN 等)
    __u16 code;            // 事件编码(如 KEY_HOME、ABS_MT_POSITION_X)
    __s32 value;           // 事件值(按键:0抬起/1按下;坐标:具体像素值)
};

Android 后续的所有事件处理,起点都是从这个结构体开始的。


四、Native 核心层:InputFlinger 三剑客

AOSP 源码中,输入系统的 Native 核心代码位于:

复制代码
frameworks/native/services/inputflinger/
├── EventHub.cpp / .h          # 设备管理与原始事件读取
├── InputReader.cpp / .h       # 事件加工与转换
├── InputDispatcher.cpp / .h   # 事件分发与超时控制
├── InputManager.cpp / .h      # 三者的协调者
├── InputListener.cpp / .h     # 监听器接口
└── InputWindow.cpp / .h       # 窗口信息

这三个核心组件运行在 system_server 进程中,各自有独立的线程:

源码路径frameworks/native/services/inputflinger/InputManager.cpp

cpp 复制代码
status_t InputManager::start() {
    // 先启动 InputDispatcher,再启动 InputReader
    status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
    if (result) {
        ALOGE("Could not start InputDispatcher thread due to error %d.", result);
        return result;
    }

    result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
    if (result) {
        ALOGE("Could not start InputReader thread due to error %d.", result);
        mDispatcherThread->requestExit();
        return result;
    }

    return OK;
}

注意两个线程都是 PRIORITY_URGENT_DISPLAY 优先级,Google 对输入响应速度要求非常高------用户每一次触摸都期望"跟手"。

4.1 EventHub:原始事件采集者

EventHub 是整个输入流水线的第一站。它的核心职责非常纯粹:

  1. 设备发现 :通过 Linux 的 inotify 机制监听 /dev/input/ 目录,当设备插入/拔出时自动感知
  2. 设备打开 :通过 ioctl 获取设备能力信息(支持哪些按键、哪些坐标轴等)
  3. 事件读取 :通过 epoll_wait 监听所有设备文件描述符,有数据时用 read() 读取 input_event
  4. 事件上报 :将读取到的原始事件封装为 RawEvent,传递给 InputReader

关键方法签名:

源码路径frameworks/native/services/inputflinger/EventHub.h

cpp 复制代码
class EventHub {
public:
    size_t getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize);
private:
    int mEpollFd;              // epoll 实例
    int mINotifyFd;            // inotify 实例
    KeyedVector<int, Device*> mDevices;  // 设备列表
};

(第二篇会详细展开 EventHub 的全部细节。)

4.2 InputReader:事件加工厂

InputReader 拿到 RawEvent 后,需要完成从"内核原始数据"到"Android 可以理解的事件"的转换。这个转换过程远比想象中复杂:

  • 一个触摸屏上报的是绝对坐标 (320, 580),但 Android 需要知道这是相对于哪个屏幕、坐标系是否需要旋转
  • 一个外接键盘的上报是 Linux 键码 KEY_A = 30,需要转换为 Android 键码 AKEYCODE_A = 29
  • 多点触控需要跟踪每个手指的 trackingId,识别 DOWN / MOVE / UP / CANCEL 等动作

InputReader 通过 InputMapper 体系 来处理不同类型的设备:

复制代码
InputReader
    └── InputDevice (代表一个物理设备)
          └── InputMapper (设备类型对应的处理策略)
                ├── SwitchInputMapper         // 开关类
                ├── KeyboardInputMapper       // 键盘
                ├── CursorInputMapper         // 鼠标/轨迹球
                ├── TouchInputMapper          // 触摸屏
                │     ├── SingleTouchInputMapper
                │     └── MultiTouchInputMapper
                ├── JoystickInputMapper       // 摇杆
                └── ExternalStylusInputMapper // 外接手写笔

加工完成后,事件被封装为 NotifyArgs 系列对象,通过 InputListener 接口传递给 InputDispatcher

(第三篇会深入拆解 InputReader 和 Mapper 体系。)

4.3 InputDispatcher:事件分发调度器

InputDispatcher 是输入系统中最关键也最容易出问题的一环。它的核心任务是:

  1. 接收加工后的事件:从 InputReader 拿到 NotifyArgs
  2. 确定目标窗口:根据当前焦点窗口 / 触摸坐标找到应该接收事件的窗口
  3. 通过 InputChannel 发送:利用 socket pair 将事件跨进程发送给 APP
  4. 超时监控与 ANR:记录每个事件的分发时间,超时则触发 ANR

一个关键的数据结构是 InputChannel:

源码路径frameworks/native/libs/input/InputTransport.cpp

cpp 复制代码
status_t InputChannel::openInputChannelPair(const String8& name,
        sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
    int sockets[2];
    socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets); // 创建 socket 对
    ...
}

system_server 通过 socket 的 server 端发送事件,APP 进程持有 client 端来接收事件。这种设计使得事件传输不经过 Binder 驱动,避免了 Binder 线程池调度带来的延迟,保证输入事件的低延迟传输。

(第四篇会全面剖析 InputDispatcher 的分发逻辑与 ANR 机制。)


五、Java 管理层:InputManagerService

InputManagerService(IMS)是输入系统在 Java 层的管理入口:

复制代码
frameworks/base/services/core/java/com/android/server/input/InputManagerService.java

它并不直接参与事件的分发和加工,而是扮演"管理者"的角色:

  • 提供 Binder 接口供外部(如 WindowManagerService、Settings 应用)调用
  • 管理输入法(IME)的切换与状态
  • 处理按键的"特殊行为"(如音量键的拦截策略、电源键的长按关机)
  • 向 Native 层传递策略配置(如 ANR 超时时长、触摸事件的坐标偏移等)

IMS 在 SystemServer 启动时被创建,与 WindowManagerService 紧密绑定------WMS 负责窗口的布局与层级,IMS 负责把事件投递到正确的窗口。


六、应用接收层:从 InputChannel 到 View.onTouchEvent

当 InputDispatcher 通过 socket 把事件发送到 APP 进程后,应用侧的处理链路是这样的:

复制代码
InputChannel (socket client 端)
    │
    ▼
NativeInputEventReceiver (JNI 层,监听 socket fd)
    │
    ▼
InputEventReceiver (Java 层,dispatchInputEvent)
    │
    ▼
ViewRootImpl.WindowInputEventReceiver (子类,处理输入事件)
    │
    ▼
ViewRootImpl.processPointerEvent / processKeyEvent
    │
    ▼
DecorView.dispatchTouchEvent / dispatchKeyEvent
    │
    ▼
Activity.dispatchTouchEvent
    │
    ▼
ViewGroup.dispatchTouchEvent → onInterceptTouchEvent → child.dispatchTouchEvent
    │
    ▼
View.onTouchEvent

其中,NativeInputEventReceiver 在 JNI 层通过向主线程的消息队列注册一个 fd 监听 来实现事件接收。这意味着 输入事件实际上是通过消息队列(MessageQueue)被注入到主线程的,它会和主线程上的其他 Message 一起排队等待处理。这也是为什么主线程卡顿会导致触摸事件延迟响应的根本原因。

(第五篇会完整展开应用侧的事件分发机制。)


七、一个触摸事件的完整旅程

用户在屏幕上点击了一个按钮:

复制代码
时间线                      事件流转
══════════════════════════════════════════════════════════════════

T0  手指触屏             电容屏感应 → 触摸IC → I2C/SPI → 内核驱动
T1  内核上报             input_event 写入 /dev/input/event2
T2  EventHub采集          epoll_wait 返回 → read() 读取 → 封装 RawEvent
T3  InputReader加工       MultiTouchInputMapper 识别 DOWN 动作
                          → 坐标从绝对像素转为逻辑坐标
                          → 生成 NotifyMotionArgs(ACTION_DOWN, x, y)
T4  InputDispatcher分发   查找触摸坐标命中的目标窗口
                          → 通过 InputChannel socket 发送 InputMessage
T5  APP进程接收           NativeInputEventReceiver 从 socket 读到数据
                          → 转换为 MotionEvent 对象
                          → post 到主线程消息队列
T6  View树分发            ViewRootImpl → DecorView → ViewGroup → Button
                          → Button.onTouchEvent(MotionEvent.ACTION_DOWN)

整个过程在正常情况下仅需 几毫秒到十几毫秒,这依赖于每一步的精巧设计:

  • 内核驱动直接写入设备节点,零拷贝
  • Native 层两个线程 PRIORITY_URGENT_DISPLAY 优先级
  • socket pair 绕开 Binder 延迟

八、关键源码文件索引

Native 层(C++)

文件路径 说明
frameworks/native/services/inputflinger/EventHub.cpp 设备发现与原始事件读取
frameworks/native/services/inputflinger/InputReader.cpp 事件加工与 Mapper 调度
frameworks/native/services/inputflinger/InputDispatcher.cpp 事件分发与 ANR 监控
frameworks/native/services/inputflinger/InputManager.cpp 三剑客的启动与协调
frameworks/native/services/inputflinger/InputListener.cpp 监听器接口
frameworks/native/libs/input/InputTransport.cpp InputChannel 与 socket 通信

Java 管理层

文件路径 说明
frameworks/base/services/core/java/com/android/server/input/InputManagerService.java IMS 主类
frameworks/base/services/core/java/com/android/server/input/InputMonitor.java 输入监控器
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java WMS,与 IMS 联动

JNI 桥接层

文件路径 说明
frameworks/base/core/jni/android_view_InputEventReceiver.cpp Native 事件接收器
frameworks/base/core/jni/android_view_KeyEvent.cpp KeyEvent JNI
frameworks/base/core/jni/android_view_MotionEvent.cpp MotionEvent JNI

应用层

文件路径 说明
frameworks/base/core/java/android/view/ViewRootImpl.java 窗口根,事件入口
frameworks/base/core/java/android/view/InputEventReceiver.java 应用侧事件接收器基类
frameworks/base/core/java/android/view/View.java View 基类,事件分发
frameworks/base/core/java/android/view/ViewGroup.java ViewGroup,事件拦截与分发

九、本系列预告

通过本篇的总览,你应该已经对 Android 7 输入系统的全貌有了清晰的认知。接下来的四篇将逐一深入:

  • 第二篇:深入 EventHub,拆解 inotify + epoll 设备监听与 RawEvent 采集机制
  • 第三篇:全面解析 InputReader 的 Mapper 体系,理解触摸、键盘、鼠标的事件加工原理
  • 第四篇:剖析 InputDispatcher 的分派策略、socket 跨进程通信与 ANR 超时判定机制
  • 第五篇:从 InputChannel 到 View.onTouchEvent(),理解应用侧事件消费的完整链路

敬请期待!