一、物流公司开业: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); // 自己处理
}
六、总结:键盘事件的完整旅程
-
硬件触发:用户按下键盘,硬件产生输入信号
-
快递员取件:InputReader 从 /dev/input 读取事件,交给 EventHub 分拣
-
调度中心分拣:InputDispatcher 根据当前激活窗口确定收件地址
-
快递运输:通过 InputChannel socket 对发送事件到应用
-
门卫签收:InputEventReceiver 接收事件,通知应用
-
屋内传递:事件在 ViewHierarchy 中传递,从 Activity 到具体 View
-
事件处理:View 处理键盘事件,回调相应的事件方法
整个过程就像一个高效的物流公司,每个环节都有明确的分工,确保键盘事件能准确、快速地送达目标应用。InputManager 作为物流公司总部,EventHub 作为分拣中心,InputDispatcher 作为调度中心,InputChannel 作为运输通道,共同构成了 Android 键盘消息处理的完整体系。