前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[017]Input子系统-上篇

[017]Input子系统-上篇

作者头像
王小二
发布2020-06-08 10:48:01
1.1K1
发布2020-06-08 10:48:01
举报

前言

还得当年我刚接触触摸屏手机的时候,我就得非常好奇,为什么我触摸屏幕会产生屏幕上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。

InputReader.cpp

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子系统-下篇中讲解

参考文章

Input系统—启动篇

Input系统—InputReader线程

Input系统—InputDispatcher线程

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • Input子系统的流程图
  • 知识准备-epoll
  • InputManagerService启动
  • Input事件的设备节点
  • InputReader的主要工作
  • InputDispatcher的主要工作
  • 总结
  • InputChannel是个什么?
  • 参考文章
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档