前言
还得当年我刚接触触摸屏手机的时候,我就得非常好奇,为什么我触摸屏幕会产生屏幕上UI的变化,感觉非常神奇。在进入这个行业之后,我才发现原来屏幕分触控层和显示层,我们触摸屏幕的事件会通过"驱动-系统-应用-应用的某个UI控件"这一个完整流程。
Input子系统的流程图
从图中可以看到一次完整的事件传递包含两个进程,system_server和app进程,我们这一篇先来分析一下左边部分,也就是system_server。
知识准备-epoll
epoll有关的知识可以看我好友的博客Linux基础知识之IO多路复用epoll
简单解释一下epoll的作用,类似于java中某个锁的wait,可以让线程block,并不占用cpu,只是epoll可以监听多个fd的状态并block,这个epoll wait之后,notify的条件是fd的内容有变化。其实Looper就是基于epoll机制,有兴趣可以看我好友博客Android P源码分析之Looper(Native)
InputManagerService启动
InputManagerService(初始化)
nativeInit
NativeInputManager
EventHub
InputManager
InputDispatcher
Looper
InputReader
QueuedInputListener
InputReaderThread
InputDispatcherThread
IMS.start(启动)
nativeStart
InputManager.start
InputReaderThread->run
InputDispatcherThread->run
简单理解就是system_server进程中的InputManagerService初始化运行以后,会启动两个线程InputReader和InputDispatcher
Input事件的设备节点
我们可以通过adb shell getevent指令看到手机上所有的input事件的设备节点,驱动层会把从屏幕上采集到触摸的事件写到 /dev/input/event1这个设备节点,其他设备节点用于处理其他事件,例如按键,摇杆。
kobewang@KobedeMacBook-Pro:~$ adb shell getevent
add device 1: /dev/input/event5
name: "sm6150-t1-snd-card Button Jack"
add device 2: /dev/input/event4
name: "sm6150-t1-snd-card Headset Jack"
add device 3: /dev/input/event3
name: "ff_key"
add device 4: /dev/input/event2
name: "gpio-keys"
add device 5: /dev/input/event0
name: "qpnp_pon"
add device 6: /dev/input/event1
name: "fts_ts"
InputReader的主要工作
InputReader通过调用EventHub的getEvents方法监听Input事件的设备节点,在getEvents方法中就会采用epoll机制进行监听,然后发送event给InputDispatcher。
void InputReader::loopOnce() {
...
//从EventHub读取事件,其中EVENT_BUFFER_SIZE = 256
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
{ // acquire lock
AutoMutex _l(mLock);
mReaderIsAliveCondition.broadcast();
if (count) { //处理事件
processEventsLocked(mEventBuffer, count);
}
if (oldGeneration != mGeneration) {
inputDevicesChanged = true;
getInputDevicesLocked(inputDevices);
}
...
} // release lock
//发送事件到nputDispatcher
mQueuedListener->flush();
}
size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
AutoMutex _l(mLock); //加锁
struct input_event readBuffer[bufferSize];
RawEvent* event = buffer; //原始事件
size_t capacity = bufferSize; //容量大小为256
bool awoken = false;
for (;;) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
...
bool deviceChanged = false;
while (mPendingEventIndex < mPendingEventCount) {
//从mPendingEventItems读取事件项
const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];
...
//获取设备ID所对应的device
ssize_t deviceIndex = mDevices.indexOfKey(eventItem.data.u32);
Device* device = mDevices.valueAt(deviceIndex);
if (eventItem.events & EPOLLIN) {
//从设备不断读取事件,放入到readBuffer
int32_t readSize = read(device->fd, readBuffer,
sizeof(struct input_event) * capacity);
if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {
deviceChanged = true;
closeDeviceLocked(device);//设备已被移除则执行关闭操作
} else if (readSize < 0) {
...
} else if ((readSize % sizeof(struct input_event)) != 0) {
...
} else {
int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
size_t count = size_t(readSize) / sizeof(struct input_event);
for (size_t i = 0; i < count; i++) {
//获取readBuffer的数据
struct input_event& iev = readBuffer[i];
//将input_event信息, 封装成RawEvent
event->when = nsecs_t(iev.time.tv_sec) * 1000000000LL
+ nsecs_t(iev.time.tv_usec) * 1000LL;
event->deviceId = deviceId;
event->type = iev.type;
event->code = iev.code;
event->value = iev.value;
event += 1;
capacity -= 1;
}
if (capacity == 0) {
mPendingEventIndex -= 1;
break;
}
}
}
...
}
...
mLock.unlock(); //poll之前先释放锁
//利用epoll机制监听input设备节点,等待input事件的到来
int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
...
mLock.lock(); //poll之后再次请求锁
...
}
return event - buffer; //返回所读取的事件个数
}
InputDispatcher的主要工作
InputDispatcher接收来自InputReader的输入事件,并记录WMS的窗口信息,用于派发事件到合适的窗口。具体的代码我就不贴了,可以看一下[015]ANR视角看InputDispatcher来理解一下InputDispatcher。
总结
一个event时间的传递的前半段旅程
第一步:驱动将屏幕的event写到了/dev/input/event1
第二步:InputReader线程通过EventHub的getEvents方法获得event事件,并通知InputDispatcher线程
第三步: InputDispatcher将这个event通过InputChannel跨进程发给App进程
InputChannel是个什么?
这个问题我们将会在[018]Input子系统-下篇中讲解
网友评论