Android 键盘消息处理:一场穿越系统的 "快递之旅"

49 阅读6分钟

一、物流公司开业:InputManager 的启动

想象 Android 系统是一家 "键盘快递物流公司",当你按下手机键盘时,一场复杂的快递流程就开始了:

1.1 总部成立:WindowManagerService 启动 InputManager

SystemServer 作为总公司老板,在启动时会下令成立 "键盘物流公司":

java

// WindowManagerService.main 公司成立仪式
public static WindowManagerService main(Context context, PowerManagerService pm) {
    WMThread thr = new WMThread(context, pm);
    thr.start(); // 启动物流公司线程
    synchronized (thr) {
        while (thr.mService == null) thr.wait(); // 等待公司注册完成
        return thr.mService; // 返回物流公司实例
    }
}

WMThread 线程在 run 方法中创建 WindowManagerService 实例,该实例会初始化 InputManager:

java

// WindowManagerService构造函数 购买物流设备
private WindowManagerService(Context context) {
    mInputManager = new InputManager(context, this); // 创建物流公司
    mInputManager.start(); // 启动物流服务
}

1.2 物流部门组建:C++ 层的核心团队

Java 层的 InputManager 通过 nativeInit 调用到 C++ 层,组建核心团队:

cpp

运行

// NativeInputManager构造函数 组建快递团队
NativeInputManager::NativeInputManager(jobject callbacks) {
    sp<EventHub> eventHub = new EventHub(); // 快递分拣中心
    mInputManager = new InputManager(eventHub, this, this); // 物流公司
}

// InputManager构造函数 分工明确
InputManager::InputManager(sp<EventHub> eventHub) {
    mDispatcher = new InputDispatcher(); // 调度中心
    mReader = new InputReader(eventHub, mDispatcher); // 快递员团队
    mReaderThread = new InputReaderThread(mReader); // 快递员工作线程
    mDispatcherThread = new InputDispatcherThread(mDispatcher); // 调度线程
}

1.3 正式运营:启动工作线程

物流公司启动两个核心线程:

cpp

运行

// InputManager.start 启动快递业务
status_t InputManager::start() {
    mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY); // 启动调度线程
    mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY); // 启动快递员线程
    return OK;
}

// InputReaderThread.threadLoop 快递员工作流程
bool InputReaderThread::threadLoop() {
    mReader->loopOnce(); // 快递员巡检一次
    return true; // 继续循环工作
}

// InputDispatcherThread.threadLoop 调度中心工作流程
bool InputDispatcherThread::threadLoop() {
    mDispatcher->dispatchOnce(); // 调度一次快递
    return true; // 继续循环工作
}

二、快递分拣中心:EventHub 的工作

2.1 扫描快递设备:发现键盘硬件

EventHub 作为分拣中心,首先扫描所有输入设备:

cpp

运行

// EventHub.getEvent 快递分拣主流程
bool EventHub::getEvent(RawEvent* outEvent) {
    if (!mOpened) {
        mOpened = true;
        scanDir("/dev/input"); // 扫描/dev/input目录
    }
    
    // 处理设备添加/移除事件...
    
    // 读取键盘事件
    for (;;) {
        if (mInputBufferIndex < mInputBufferCount) {
            // 处理已缓冲的事件
            const struct input_event& iev = mInputBufferData[mInputBufferIndex++];
            // 解析事件类型、按键码等信息
            outEvent->type = iev.type;
            outEvent->scanCode = iev.code;
            outEvent->value = iev.value;
            outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC);
            return true;
        }
        
        // 没有事件时等待新事件
        mInputDeviceIndex = 0;
        release_wake_lock(); // 释放唤醒锁
        int pollResult = poll(mFDs, mFDCount, -1); // 等待事件
        acquire_wake_lock(); // 重新获取唤醒锁
    }
}

2.2 打开键盘设备:建立连接

cpp

运行

// EventHub.scanDir 扫描设备目录
int EventHub::scanDir(const char* dirname) {
    DIR* dir = opendir(dirname);
    while ((de = readdir(dir))) {
        if (de->d_name[0] == '.') continue; // 跳过隐藏文件
        strcpy(filename, de->d_name);
        openDevice(devname); // 打开设备文件
    }
    closedir(dir);
    return 0;
}

// EventHub.openDevice 打开具体设备
int EventHub::openDevice(const char* deviceName) {
    int fd = open(deviceName, O_RDWR | O_CLOEXEC); // 打开设备文件
    // 获取设备名称、配置按键映射等...
    
    // 注册到epoll监控
    struct epoll_event eventItem;
    eventItem.events = EPOLLIN;
    eventItem.data.u32 = deviceId;
    epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem);
    
    return 0;
}

三、快递调度中心:InputDispatcher 的工作

3.1 接收快递任务:处理键盘事件

当快递员(InputReader)收到键盘事件后,调度中心(InputDispatcher)开始工作:

cpp

运行

// InputReader.loopOnce 快递员处理事件
void InputReader::loopOnce() {
    mEventHub->getEvent(&rawEvent); // 获取键盘事件
    process(&rawEvent); // 处理事件
}

// InputReader.process 事件预处理
void InputReader::process(RawEvent* rawEvent) {
    if (rawEvent->type == EV_KEY) { // 键盘事件
        int32_t keyCode = AKEYCODE_UNKNOWN;
        uint32_t flags = 0;
        mEventHub->mapKey(rawEvent->deviceId, rawEvent->scanCode, &keyCode, &flags); // 映射按键码
        NotifyKeyArgs args(/* 事件参数 */);
        mListener->notifyKey(&args); // 通知调度中心
    }
}

// InputDispatcher.notifyKey 调度中心接收事件
void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
    KeyEntry* newEntry = new KeyEntry(/* 事件信息 */);
    enqueueInboundEventLocked(newEntry); // 将事件加入队列
    mLooper->wake(); // 唤醒调度线程
}

3.2 调度快递路线:分发给目标应用

调度中心根据当前激活窗口,确定快递地址:

cpp

运行

// InputDispatcher.dispatchOnceInnerLocked 核心调度逻辑
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    if (mInboundQueue.isEmpty()) return;
    
    mPendingEvent = mInboundQueue.dequeueAtHead(); // 取出事件
    
    switch (mPendingEvent->type) {
        case EventEntry::TYPE_KEY: {
            KeyEntry* keyEntry = static_cast<KeyEntry*>(mPendingEvent);
            dispatchKeyLocked(keyEntry, nextWakeupTime); // 分发键盘事件
            break;
        }
    }
}

// InputDispatcher.dispatchKeyLocked 确定收件地址
bool InputDispatcher::dispatchKeyLocked(KeyEntry* entry, nsecs_t* nextWakeupTime) {
    Vector<InputTarget> inputTargets;
    int32_t injectionResult = findFocusedWindowTargetsLocked(entry, inputTargets, nextWakeupTime);
    if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) return true;
    
    dispatchEventLocked(entry, inputTargets); // 按地址分发
    return true;
}

// InputDispatcher.dispatchEventLocked 执行分发
void InputDispatcher::dispatchEventLocked(EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) {
    for (const InputTarget& target : inputTargets) {
        ssize_t connectionIndex = getConnectionIndexLocked(target.inputChannel);
        if (connectionIndex >= 0) {
            sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
            enqueueDispatchEntriesLocked(connection, eventEntry, &target); // 加入发送队列
            startDispatchCycleLocked(connection); // 开始发送
        }
    }
}

四、申请收货地址:应用注册 InputChannel

4.1 开设收货点:ViewRootImpl 申请通道

应用窗口需要先申请 "收货地址"(InputChannel)才能接收键盘快递:

java

// ViewRootImpl.setView 申请收货地址
public void setView(View view, WindowManager.LayoutParams attrs) {
    mWindowSession.addToDisplay(mWindow, mSeq, attrs, mInputChannel); // 申请输入通道
    if (mInputChannel != null) {
        mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, Looper.myLooper());
    }
}

// WMS.addWindow 总部登记地址
public int addWindow(Session session, IWindow client, InputChannel outInputChannel) {
    synchronized (mWindowMap) {
        InputChannel[] inputChannels = InputChannel.openInputChannelPair(name); // 创建双向通道
        win.setInputChannel(inputChannels[0]); // 服务器端通道
        inputChannels[1].transferTo(outInputChannel); // 客户端通道给应用
        mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle); // 注册到InputManager
    }
}

4.2 地址登记:InputDispatcher 记录地址

cpp

运行

// InputDispatcher.registerInputChannel 登记收货地址
status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel, 
                                              const sp<InputWindowHandle>& inputWindowHandle) {
    sp<Connection> connection = new Connection(inputChannel, inputWindowHandle);
    int fd = inputChannel->getFd();
    mConnectionsByFd.add(fd, connection); // 记录地址
    
    mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this); // 监听该地址
    mLooper->wake(); // 唤醒监听
    return OK;
}

五、快递签收:应用处理键盘事件

5.1 门卫签收:InputEventReceiver 接收事件

应用端的 "门卫" 接收快递并通知屋内:

java

// NativeInputEventReceiver.handleEvent 门卫通知
int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {
    consumeEvents(env, false, -1); // 签收快递
    return status == OK ? 1 : 0;
}

// InputEventReceiver.dispatchInputEvent 通知屋内
public void dispatchInputEvent(InputEvent event) {
    onInputEvent(event); // 交给窗口处理
}

// WindowInputEventReceiver.onInputEvent 窗口接收
public void onInputEvent(InputEvent event) {
    enqueueInputEvent(event, this, 0, true); // 加入事件队列
}

5.2 屋内处理:事件在 ViewHierarchy 中传递

java

// ViewRootImpl.doProcessInputEvents 屋内分拣
void doProcessInputEvents() {
    while (mFirstPendingInputEvent != null) {
        QueuedInputEvent q = mFirstPendingInputEvent;
        mFirstPendingInputEvent = q.mNext;
        deliverInputEvent(q); // 交付事件
    }
}

// ViewRootImpl.deliverKeyEvent 处理键盘事件
private void deliverKeyEvent(QueuedInputEvent q) {
    KeyEvent event = (KeyEvent)q.mEvent;
    if (mView.dispatchKeyEventPreIme(event)) { // 先给输入法处理
        finishInputEvent(q, true);
        return;
    }
    deliverKeyEventPostIme(q); // 再给视图处理
}

// Activity.dispatchKeyEvent 活动接收事件
public boolean dispatchKeyEvent(KeyEvent event) {
    Window win = getWindow();
    if (win.superDispatchKeyEvent(event)) { // 窗口先处理
        return true;
    }
    View decor = mDecor;
    return event.dispatch(this, decor != null ? decor.getKeyDispatcherState() : null, this);
}

// View.dispatchKeyEvent 视图处理事件
public boolean dispatchKeyEvent(KeyEvent event) {
    if (mOnKeyListener != null && mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
        return true;
    }
    return onKeyEvent(event); // 自己处理
}

六、总结:键盘事件的完整旅程

  1. 硬件触发:用户按下键盘,硬件产生输入信号

  2. 快递员取件:InputReader 从 /dev/input 读取事件,交给 EventHub 分拣

  3. 调度中心分拣:InputDispatcher 根据当前激活窗口确定收件地址

  4. 快递运输:通过 InputChannel socket 对发送事件到应用

  5. 门卫签收:InputEventReceiver 接收事件,通知应用

  6. 屋内传递:事件在 ViewHierarchy 中传递,从 Activity 到具体 View

  7. 事件处理:View 处理键盘事件,回调相应的事件方法

整个过程就像一个高效的物流公司,每个环节都有明确的分工,确保键盘事件能准确、快速地送达目标应用。InputManager 作为物流公司总部,EventHub 作为分拣中心,InputDispatcher 作为调度中心,InputChannel 作为运输通道,共同构成了 Android 键盘消息处理的完整体系。