一、物流公司架构:从总部到快递员
想象 Android 输入系统是一家 "触摸物流公司",专门处理用户的触摸、键盘等输入 "包裹":
-
InputManagerService (IMS) 是物流公司总部,坐在顶层办公室(SystemServer 进程)负责全局调度
-
InputReader 是前线快递员,蹲守在硬件门口(/dev/input)收集包裹
-
InputDispatcher 是调度中心,负责将包裹按地址分发给正确的收件人
-
应用程序 是最终收件人,每个窗口都是一个收货地址
总部与快递员之间通过一套复杂的物流系统协作,而每个应用窗口都有专属的 "收货地址"(InputChannel)。
二、物流公司开业:输入系统启动流程
2.1 总部成立:InputManagerService 的启动
当 Android 系统启动时,SystemServer 作为总公司老板,会下令成立 "触摸物流公司":
java
// SystemServer.java中启动输入服务
InputManagerService inputManager = new InputManagerService(context, handler);
inputManager.start(); // 物流公司正式开业
InputManagerService 的启动就像总部大楼的奠基,它会创建两个核心部门:
- InputReader 部门:负责从硬件读取输入事件,像快递员在仓库门口收包裹
- InputDispatcher 部门:负责调度分发事件,像调度中心安排配送路线
2.2 快递员上岗:InputReader 的工作准备
InputReader 快递员上岗后,会在仓库门口(/dev/input)安装一个监控摄像头(epoll),实时监控硬件设备:
cpp
运行
// InputReader.loopOnce 快递员巡检流程
void InputReader::loopOnce() {
// 检查是否有新包裹需要处理
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
if (count > 0) {
processEventsLocked(mEventBuffer, count); // 处理收到的包裹
}
}
EventHub 就像包裹分拣中心,负责扫描所有输入设备(/dev/input 下的文件),当有触摸或键盘事件时,就像新包裹到达,EventHub 会通知 InputReader:
cpp
运行
// EventHub.scanDevicesLocked 扫描可用设备
void EventHub::scanDevicesLocked() {
DIR *dir = opendir("/dev/input"); // 打开仓库大门
while ((de = readdir(dir)) != NULL) {
if (de->d_name[0] == '.') continue;
openDeviceLocked(devname); // 打开设备文件,注册监控
}
}
2.3 调度中心运作:InputDispatcher 的调度逻辑
InputDispatcher 调度中心拿到包裹后,会查看收件地址簿(mWindowHandles),确定该发给哪个应用窗口:
cpp
运行
// InputDispatcher.dispatchOnceInnerLocked 调度核心逻辑
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
if (mInboundQueue.isEmpty()) {
// 没有包裹,休息一下
} else {
mPendingEvent = mInboundQueue.dequeueAtHead(); // 取出包裹
switch (mPendingEvent->type) {
case EventEntry::TYPE_KEY:
dispatchKeyLocked(...); // 分发键盘包裹
break;
case EventEntry::TYPE_MOTION:
dispatchMotionLocked(...); // 分发触摸包裹
break;
}
}
}
三、建立物流通道:InputChannel 的注册过程
每个应用窗口需要先申请一个 "收货地址"(InputChannel),才能收到输入包裹。这个过程就像用户在物流公司登记收货地址:
3.1 申请收货地址:ViewRootImpl 的注册
当 Activity 创建窗口时,会通过 ViewRootImpl 向 WMS 申请一个 InputChannel:
java
// ViewRootImpl.setView 申请收货地址
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
mWindowSession.addToDisplay(... , mInputChannel); // 申请输入通道
if (mInputChannel != null) {
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, Looper.myLooper());
}
}
WMS 收到申请后,会创建一对 "双向快递通道"(socket pair),一个给调度中心(Server 端),一个给应用(Client 端):
cpp
运行
// InputChannel.openInputChannelPair 创建双向通道
status_t InputChannel::openInputChannelPair(const String8& name,
sp<InputChannel>& serverChannel,
sp<InputChannel>& clientChannel) {
int sockets[2];
socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets); // 创建socket对
// 配置通道参数
serverChannel = new InputChannel(name + "(server)", sockets[0]);
clientChannel = new InputChannel(name + "(client)", sockets[1]);
return OK;
}
3.2 地址登记:InputDispatcher 注册通道
WMS 将 Server 端通道注册到 InputDispatcher 的地址簿中,就像物流公司登记收件地址:
cpp
运行
// InputDispatcher.registerInputChannel 登记地址
status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel,
const sp<InputWindowHandle>& inputWindowHandle,
bool monitor) {
sp<Connection> connection = new Connection(inputChannel, inputWindowHandle, monitor);
int fd = inputChannel->getFd();
mConnectionsByFd.add(fd, connection); // 记录地址
mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this); // 监听该地址
return OK;
}
3.3 收件人准备:应用端通道初始化
应用端的 Client 通道会绑定到 InputEventReceiver,就像收件人告诉物流公司把包裹送到哪个门卫处:
java
// InputEventReceiver 收件人门卫
public InputEventReceiver(InputChannel inputChannel, Looper looper) {
mInputChannel = inputChannel;
mMessageQueue = looper.getQueue();
mReceiverPtr = nativeInit(this, inputChannel, mMessageQueue); // 初始化门卫
}
四、包裹配送:输入事件的分发过程
4.1 快递员取件:InputReader 获取事件
当用户触摸屏幕或按下键盘,InputReader 快递员会从硬件仓库取出包裹:
cpp
运行
// EventHub.getEvents 从硬件读取事件
size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
int fd = device->fd;
int readSize = read(fd, readBuffer, sizeof(struct input_event) * capacity);
// 解析输入事件,放入buffer
for (size_t i=0; i<count; i++) {
event->type = readBuffer[i].type;
event->code = readBuffer[i].code;
event->value = readBuffer[i].value;
event++;
}
return event - buffer; // 返回包裹数量
}
4.2 调度中心分拣:InputDispatcher 处理事件
InputDispatcher 拿到包裹后,会根据地址簿找到对应的应用窗口:
cpp
运行
// InputDispatcher.dispatchKeyLocked 分发键盘事件
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {
Vector<InputTarget> inputTargets;
int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,
entry, inputTargets, nextWakeupTime);
dispatchEventLocked(currentTime, entry, inputTargets); // 按地址分发
return true;
}
4.3 包裹运输:通过 InputChannel 发送事件
调度中心通过 Server 端 InputChannel 将包裹发送出去,就像物流公司用卡车运输包裹:
cpp
运行
// InputPublisher.publishKeyEvent 发送键盘事件包裹
status_t InputPublisher::publishKeyEvent(...) {
InputMessage msg;
msg.header.type = InputMessage::TYPE_KEY;
// 填充包裹内容(事件参数)
return mChannel->sendMessage(&msg); // 通过通道发送
}
// InputChannel.sendMessage 通道运输
status_t InputChannel::sendMessage(const InputMessage* msg) {
ssize_t nWrite = send(mFd, msg, msg->size(), MSG_DONTWAIT | MSG_NOSIGNAL);
return nWrite >= 0 ? OK : -errno;
}
4.4 收件人签收:应用处理事件
应用端的 InputEventReceiver 门卫收到包裹后,会通知屋里的人(ViewHierarchy)来处理:
java
// NativeInputEventReceiver.handleEvent 门卫通知
int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {
consumeEvents(env, false, -1); // 消费事件包裹
return status == OK || status == NO_MEMORY ? 1 : 0;
}
// ViewRootImpl.deliverKeyEvent 事件送达应用
private void deliverKeyEvent(QueuedInputEvent q) {
KeyEvent event = (KeyEvent)q.mEvent;
if (mView.dispatchKeyEventPreIme(event)) { // 先给输入法处理
finishInputEvent(q, true);
return;
}
deliverKeyEventPostIme(q); // 再给应用视图处理
}
4.5 视图处理:事件在 ViewHierarchy 中的传递
事件包裹最终在 ViewHierarchy 中传递,就像包裹在家庭内部传递:
java
// 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); // 自己处理
}
五、特殊包裹:软键盘事件的分发
当用户点击输入框时,就像需要特殊处理的 "加急包裹",需要输入法参与:
-
申请加急处理:View 获得焦点后,会通知 InputMethodManager 需要输入法
java
// TextView请求输入法
public void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(focused, direction, previouslyFocusedRect);
if (focused) {
requestFocusFromTouch();
mInputMethodManager.startInput(this, mInputConnection); // 启动输入法
}
}
-
输入法处理:输入法通过 InputConnection 接收用户输入的字符
cpp
运行
// InputMethodService.commitText 输入法提交文本
public void commitText(CharSequence text, int newCursorPosition) {
mInputConnection.commitText(text, newCursorPosition); // 提交到文本框
}
-
特殊通道传递:输入法提交的文本会作为特殊包裹,跳过输入法处理阶段
java
// ViewRootImpl处理输入法提交的事件
private void deliverKeyEventPostIme(QueuedInputEvent q) {
if (mView.dispatchKeyEvent(event)) { // 直接给视图处理
finishInputEvent(q, true);
return;
}
finishInputEvent(q, false);
}
六、总结:输入事件的完整旅程
-
用户操作:触摸屏幕或按下键盘,硬件产生输入信号
-
快递员取件:InputReader 从 /dev/input 读取事件,封装成包裹
-
调度中心分拣:InputDispatcher 根据焦点窗口确定收件地址
-
通道运输:通过 InputChannel socket 对发送事件到应用
-
门卫签收:InputEventReceiver 接收事件,通知应用
-
视图处理:事件在 ViewHierarchy 中传递,从 Activity 到具体 View
-
特殊情况:软键盘事件由输入法处理后,再提交给文本框
整个过程就像一个高效的物流公司,从收集包裹到分拣配送,再到收件人签收,每个环节都有明确的分工和流程,确保用户的每一次操作都能准确送达目标应用。InputChannel 就像物流单号,InputDispatcher 像调度系统,共同保证了输入事件的高效分发。