[Android] Input事件分发流程之InputReader(2)

news/2024/4/19 0:48:48

继IMS构造方法分析完成后,看看IMS中的start方法

public void start() {Slog.i(TAG, "Starting input manager");// 之前初始化了InputManager->inputDispatcher&&inputReader// 这里开始start它们,并且会创建InputThread线程,也就是InputReaderThread和InputDispatcherThread// InputThread是一个自定义的类,它接受三个参数:一个字符串作为线程名,// 一个lambda表达式作为线程函数,另一个lambda表达式在线程结束时被调用nativeStart(mPtr);// Add ourself to the Watchdog monitors.Watchdog.getInstance().addMonitor(this);//注册触摸点速度和是否显示功能的观察者registerPointerSpeedSettingObserver();registerShowTouchesSettingObserver();...// 更新触摸点的速度updatePointerSpeedFromSettings();// 是否在屏幕上显示触摸点updateShowTouchesFromSettings();updateAccessibilityLargePointerFromSettings();updateDeepPressStatusFromSettings("just booted");updateMaximumObscuringOpacityForTouchFromSettings();updateBlockUntrustedTouchesModeFromSettings();}

以上只关注nativeStart(mPtr);,mPtr指向的就是NativeInputManager,之前初始化了InputManager->inputDispatcher&&inputReader,这里开始start它们,并且会创建InputThread线程,也就是InputReaderThread和InputDispatcherThread

// **com_android_server_input_InputManagerService.cpp**
static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);// 获取InputManager,调用它的startstatus_t result = im->getInputManager()->start();if (result) {jniThrowRuntimeException(env, "Input manager could not be started.");}
}// **InputManager.cpp**
status_t InputManager::start() {// 调用inputDispatcher的start()status_t result = mDispatcher->start();if (result) {ALOGE("Could not start InputDispatcher thread due to error %d.", result);return result;}// 调用inputReader的start()result = mReader->start();if (result) {ALOGE("Could not start InputReader due to error %d.", result);mDispatcher->stop();return result;}return OK;
}

InputReader线程

也就是在InputManager中调用了mDispatcher->start();和mReader->start();去分别创建对应的线程,并让Inputeader和InputDispatcher执行具体逻辑。先来看看InputReader中的start方法

status_t InputReader::start() {if (mThread) {return ALREADY_EXISTS;}// InputThread是一个自定义的类,它接受三个参数:一个字符串作为线程名,一个lambda表达式作为线程函数,// 另一个lambda表达式在线程结束时被调用mThread = std::make_unique<InputThread>("InputReader", [this]() { loopOnce(); }, [this]() { mEventHub->wake(); });return OK;
}// 这里就是start后的主要内容
// 读取eventHub里面的设备节点内容
// 调用的是 mEventHub->getEvents
void InputReader::loopOnce() {int32_t oldGeneration;int32_t timeoutMillis;bool inputDevicesChanged = false;...// 读取设备里的所有事件,存放到了mEventBuffer// RawEvent mEventBuffer[EVENT_BUFFER_SIZE] GUARDED_BY(mLock);size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);{ // acquire lock...if (count) {// 关键代码processEventsLocked(mEventBuffer, count);}...mQueuedListener->flush();
}

首先调用了mEventHub中的getEvents方法其获取输入的原始事件,并存放到RawEvent中,所以该实体类中存放的是原始输入事件,然后调用了之前初始化创建的mQueuedListener,调用flush()将事件发送给InputDispatcher中,继续看看EventHub中的getEvents方法

size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer/*缓存事件*/, size_t bufferSize/*256*/) {struct input_event readBuffer[bufferSize];// 原始事件RawEvent* event = buffer;// 256size_t capacity = bufferSize;bool awoken = false;// 循环读取设备节点for (;;) {...// std::vector<std::unique_ptr<Device>> mOpeningDevices;// 初始化动作,主要是打开设备和扫描设备// 扫描设备,并打开每个设备目录/dev/input/...scanDevicesLocked();while (!mOpeningDevices.empty()) {// 获取mOpeningDevices中的设备/dev/input/目录下的文件:event1/2/3/4等std::unique_ptr<Device> device = std::move(*mOpeningDevices.rbegin());mOpeningDevices.pop_back();ALOGV("Reporting device opened: id=%d, name=%s\n", device->id, device->path.c_str());// 将每个设备打包生成RawEvent(event)形式进行存储对应的信息event->when = now;event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;// 添加设备的事件event->type = DEVICE_ADDED;event += 1;...}// 这里是开始处理每个设备中的事件while (mPendingEventIndex < mPendingEventCount) {// 从mPendingEventCount读取事件const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];...// 打开/dev/input/... event1/2/3等设备文件Device* device = getDeviceByFdLocked(eventItem.data.fd);if (device == nullptr) {ALOGE("Received unexpected epoll event 0x%08x for unknown fd %d.", eventItem.events,eventItem.data.fd);ALOG_ASSERT(!DEBUG);continue;}...// This must be an input event// 读取设备中的输入事件if (eventItem.events & EPOLLIN) {// 读取设备里面的事件内容int32_t readSize =read(device->fd, readBuffer, sizeof(struct input_event) * capacity);if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {// Device was removed before INotify noticed.ALOGW("could not get event, removed? (fd: %d size: %" PRId32" bufferSize: %zu capacity: %zu errno: %d)\n",device->fd, readSize, bufferSize, capacity, errno);deviceChanged = true;// 没有事件则关闭设备closeDeviceLocked(*device);} ... 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++) {// 将每个事件打包生成RawEventstruct input_event& iev = readBuffer[i];event->when = processEventTimestamp(iev);event->readTime = systemTime(SYSTEM_TIME_MONOTONIC);event->deviceId = deviceId;event->type = iev.type;event->code = iev.code;event->value = iev.value;event += 1;capacity -= 1;}...}} ...}...// poll之前先释放锁mLock.unlock(); // release lock before poll// 等待input事件的到来int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);// 事件到来就请求锁mLock.lock(); // reacquire lock after poll...// All done, return the number of events we read.// 返回所读取的事件个数return event - buffer;
}

以上内容就是scanDevicesLocked做初始化动作,扫描设备,打开设备,将设备保存到mOpeningDevices中,然后循环读取每个设备中的事件,将每个事件封装成RawEvent并加上了DEVICE_ADDED这个标志(后面会用到,来决定是移除还是新增),再保存到buffer(mEventBuffer)里面,如果设备中没有事件了则会关闭设备,然后通过epoll_wait来一直等等下一次的input事件到来,所以我们回到InputReader.cpp#loopOnce中继续分析processEventsLocked(mEventBuffer, count);

void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {// rawEvents保存了通过EventHub中获取的设备里的事件// count是事件数量for (const RawEvent* rawEvent = rawEvents; count;) {int32_t type = rawEvent->type;size_t batchSize = 1;if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {...// 事件的处理processEventsForDeviceLocked(deviceId, rawEvent, batchSize);} else {// 在EventHub.getEvents中有处理,每个事件都会封装为RawEvent,并且而加上了DEVICE_ADDED标志switch (rawEvent->type) {case EventHubInterface::DEVICE_ADDED:// 创建设备,eventHubId设备唯一id,通过id可获取设备// 这里主要是创建InputDevice,根据device判断是鼠标类型触摸事件,还是键盘类,或触摸屏类addDeviceLocked(rawEvent->when, rawEvent->deviceId);break;case EventHubInterface::DEVICE_REMOVED:removeDeviceLocked(rawEvent->when, rawEvent->deviceId);break;case EventHubInterface::FINISHED_DEVICE_SCAN:handleConfigurationChangedLocked(rawEvent->when);break;default:ALOG_ASSERT(false); // can't happenbreak;}}// 处理完事件,count减少count -= batchSize;// 处理完事件,指针向后移动一个rawEvent += batchSize;}
}void InputReader::processEventsForDeviceLocked(int32_t eventHubId, const RawEvent* rawEvents,size_t count) {auto deviceIt = mDevices.find(eventHubId);...// 调用InputDevice的process// 传递原始事件+事件数量device->process(rawEvents, count);
}

首先会走addDeviceLocked来创建InputDevice,因为/dev/input里每个device的功能都不同,有的处理指纹事件,有的处理鼠标键盘类数据,有的处理屏幕触摸事件,然后再执行到processEventsForDeviceLocked,因为processEventsForDeviceLocked执行之前已经创建了各个类型的InputDevice,这里就是调用对应类型的InputDevice.process方法,那随便找一个TouchInputMapper.cpp来分析吧,触摸事件

frameworks/native/services/inputflinger/reader/mapper/TouchInputMapper.cpp

void TouchInputMapper::process(const RawEvent* rawEvent/*原始事件*/) {// 鼠标按下,滚动,触摸mCursorButtonAccumulator.process(rawEvent);mCursorScrollAccumulator.process(rawEvent);mTouchButtonAccumulator.process(rawEvent);if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {// 主要关注syncsync(rawEvent->when, rawEvent->readTime);}
}

sync会继续调用processRawTouches,接着看

// 它处理原始触摸输入数据并将其转换为可读的触摸事件。
// 首先,它检查设备模式是否为DISABLED。如果是,它将取消当前的触摸,并清空当前的状态(mCurrentCookedState),然后更新触摸点。
// 接下来,它会处理任何待处理的原始触摸状态。这些状态被存储在mRawStatesPending中。
// 如果当前状态是一个stylus(触笔)状态,则会检查是否需要等待stylus数据。
// 如果需要等待,它将退出循环。否则,它会将当前状态设为下一个待处理状态,并通过cookAndDispatch函数将其转换为可读的触摸事件
void TouchInputMapper::processRawTouches(bool timeout) {// 检查设备是否处于disabled状态if (mDeviceMode == DeviceMode::DISABLED) {// Drop all input if the device is disabled.cancelTouch(mCurrentRawState.when, mCurrentRawState.readTime);...return;}// 它会处理任何待处理的原始触摸状态,这些状态被存储在mRawStatesPending中。const size_t N = mRawStatesPending.size();size_t count;// 待处理的原始事件 RawEventfor (count = 0; count < N; count++) {...// 继续调用,主要就是将原始事件转换为可读的触摸事件cookAndDispatch(mCurrentRawState.when, mCurrentRawState.readTime);}...
}

简要说明一下cookAndDispatch方法的作用,判断事件的类型属于手势触摸还是鼠标等类型,假设是手势触摸则调用dispatchPointerGestures进行事件分发,然后判断事件是否移动,tap按下等状态,进行单独处理和事件分发,并把当前手指通过markBit(id);存入一个整型变量中,所以不管有多少个手指触摸,都以进制形式存放在一个变量中,内存开销小,最后调用dispatchMotion方法将事件的状态,例如是否按下,按下的时间,事件动作,以及事件id封装到NotifyMotionArgs中,然后进行分发(也就是通知消息并携带该args给InputDispatcher),实际是调用getListener()->notifyMotion(&args);

所以继续看看InputDispatcher.cpp中的notifyMotion方法,进入下一个阶段

总结:

  1. 从InputReader线程创建开始,第一时间就是从EventHub中的getEvents方法中打开/dev/input中的设备,然后循环读取设备中的事件,获取事件,并转换为原始事件(RawEvent),然后再将原始事件,此次事件处理完成后继续循环等待input事件的到来

2.然后再继续调用了processEventsLocked判断原始事件属于什么类型,例如触摸,鼠标,键盘等,创建对应的InputDevice

  1. 接着继续调用processEventsForDeviceLocked来处理InputDevice→process,然后继续分析了TouchInputMapper.cpp,因为这个mapper就是InputDevice的其中一种类型,根据事件类型,判断是否点击,滑动,多个手势,将事件信息封装成NotifyMotionArgs,将手指封装到整型变量中,代表有多少个手指,然后分发给InputDispatcher

http://www.ppmy.cn/news/1141105.html

相关文章

Acwing.790 数的三次方根

题目 给定一个浮点数n&#xff0c;求它的三次方根。 输入格式 共一行&#xff0c;包含一个浮点数n。 输出格式 共—行&#xff0c;包含一个浮点数&#xff0c;表示问题的解。注意&#xff0c;结果保留6位小数。 数据范围 -10000 ≤n ≤10000 输入样例: 1000.00输出样例…

竞赛 机器学习股票大数据量化分析与预测系统 - python 竞赛

文章目录 0 前言1 课题背景2 实现效果UI界面设计web预测界面RSRS选股界面 3 软件架构4 工具介绍Flask框架MySQL数据库LSTM 5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 机器学习股票大数据量化分析与预测系统 该项目较为新颖&am…

线性表相关知识

1.简述 线性表&#xff0c;全名为线性存储结构。使用线性表存储数据的方式可以这样理解&#xff0c;即“把所有数据按照顺序&#xff08;线性&#xff09;的存储结构方式&#xff0c;存储在物理空间”。 按照空间分类&#xff1a; 顺序存储结构&#xff1a;数据依次存储在连续…

SQLServer安装

SQL Server安装指南&#xff1a;从下载到配置 SQL Server是一款强大的关系型数据库管理系统&#xff0c;广泛应用于企业和组织中&#xff0c;以其卓越的性能和丰富的功能而闻名。但要充分利用SQL Server的潜力&#xff0c;首先需要正确安装和配置它。在这篇博客中&#xff0c;我…

2023年10月8日

三盏灯流水 .text .global _start _start: 1.设置GPIOE寄存器的时钟使能 RCC_MP_AHB4ENSETR[5:4]->1 0x50000a28 LDR R0,0X50000A28 LDR R1,[R0] 从r0为起始地址的4字节数据取出放在R1 ORR R1,R1,#(0x3<<4) 第4位设置为1 STR R1,[R0] 写回2.设置PE10管脚为…

LED灯亮灭

.text .global _start _start: 设置GPIO寄存器的时钟使能  RCC_MP_AHB4ENSETR[4]->1 0x50000a28LDR R0,0x50000A28LDR R1,[R0] 从R0为起始地址的&#xff14;个字节数据取出放入R1中ORR R1,R1,#(0x1<<4) 第四位设置为1STR R1,[R0] 写回LDR R0,0x5000…

接口测试常见问题

1.接口测试的流程 测试计划与方案 --> 接口用例设计 --> 接口测试执行 --> 缺陷报告与结果分析 2.接口工具的流程 脚本的设计&#xff0c;数据用例的设计&#xff0c;断言&#xff08;预期结果的设计&#xff09;&#xff0c;执行 3.测试计划与方案&#xff1a; …

买房需要了解的一些事

注&#xff1a;本文写于 2021年06月26日 是当时个人的看法&#xff0c; 因前段时间公众号全清空删除了&#xff0c;现在重新发出来。 近段时间二手房贷款全面收紧&#xff0c;对炒房行为应该能起到一定的抑制作用&#xff0c;个人觉得这是好事&#xff0c;&#xff08;炒房客不…

Spring Cloud Gateway2之断言Predicate详解

文章目录 1. 前言2. Spring Cloud Gateway断言的种类及各自功能2.1. Path断言 PathRoutePredicateFactory2.2.Method断言 MethodRoutePredicateFactory2.3.Header断言 HeaderRoutePredicateFactory2.4.Host断言 HostRoutePredicateFactory2.5.Query断言 QueryRoutePredicateFac…

sparksql 中的concat_ws 和sort_array 和collect_list的使用方法

1. concat_ws函数&#xff1a; - concat_ws用于将多个字符串连接成一个以指定分隔符分隔的单个字符串。 - 语法&#xff1a;concat_ws(separator, str1, str2, ...) - 示例&#xff1a; sql SELECT concat_ws(,, apple, banana, cherry) AS fruits; …

Vue14 监视属性简写

监视属性简写 当监视属性只有handler时&#xff0c;可以使用简写 <!DOCTYPE html> <html><head><meta charset"UTF-8" /><title>天气案例_监视属性_简写</title><!-- 引入Vue --><script type"text/javascript&…

yolov8封装进入ROS系统

一,yolov8的环境配置工作 说明:需要相关工程全部源码的可私聊博主或评论留言 配置工作可以参考我的一篇关于yolov8环境配置的博客。 需要说明的是:直接使用: pip install ultralytics 会导致后面对于网络结构的改进无法运行。所以如果需要对网络结构进行改进的同学,不要使用…

ElementUI结合Vue完成主页的CUD(增删改)表单验证

目录 一、CUD ( 1 ) CU讲述 ( 2 ) 编写 1. CU 2. 删除 二、验证 前端整合代码 : 一、CUD 以下的代码基于我博客中的代码进行续写 : 使用ElementUI结合Vue导航菜单和后台数据分页查询 ( 1 ) CU讲述 在CRUD操作中&#xff0c;CU代表创建&#xff08;Create&#xff09…

渗透测试信息收集方法笔记

一、指纹识别 1、钟馗之眼https://www.zoomeye.org/ 2、天眼查https://www.tianyancha.com/ 3、工具&#xff1a;御剑WEB指纹识别系统正式版&#xff0c;可以查网站用了哪些框架&#xff0c;什么版本&#xff0c;有哪些漏洞 4、kali whatweb 二、信息泄露 1、csdn https://www.…

《Linux 内核设计与实现》13. 虚拟文件系统

通用文件接口 VFS 使得可以直接使用 open()、read()、write() 这样的系统调用而无需考虑具体文件系统和实际物理介质。 好处&#xff1a;新的文件系统和新类型的存储介质需要挂载时&#xff0c;程序无需重写&#xff0c;甚至无需重新编译。 VFS 将各种不同的文件系统抽象后采…

Electron笔记

基础环境搭建 官网:https://www.electronjs.org/zh/ 这一套笔记根据这套视频而写的 创建项目 方式一: 官网点击GitHub往下拉找到快速入门就能看到下面这几个命令了 git clone https://github.com/electron/electron-quick-start //克隆项目 cd electron-quick-start //…

使用Docker安装JupyterHub

安装JupyterHub 拉取Jupyter镜像并运行容器 docker run -d -p 8000:8000 --name jupyterhub jupyterhub/jupyterhub jupyterhub # -d&#xff1a;后台运行 # -p 8000:8000&#xff1a;宿主机的8000端口映射容器中的8000端口 # --name jupyterhub&#xff1a;给运行的容器起名…

想要精通算法和SQL的成长之路 - 简化路径

想要精通算法和SQL的成长之路 - 简化路径 前言一. 简化路径 前言 想要精通算法和SQL的成长之路 - 系列导航 一. 简化路径 原题连接 思路如下&#xff1a; 我们根据 "/" 去拆分字符串&#xff0c;得到每个子目录。这里拿到的子目录可能是空字符串&#xff0c;需要…

使用Windows系统自带的安全加密解密文件操作步骤详解

原以为安全加密的方法是加密压缩包&#xff0c;有的需要用软件加密文件&#xff0c;可每次想往里面修改或存放文件都要先解密&#xff0c;不用时&#xff0c;还得去加密&#xff0c;操作步骤那么多&#xff0c;那多不方便呀&#xff0c;这里讲讲用系统自带的BitLocker加密工具怎…

软件测试面试常常遇到的6大“套路”!

前言 面试中&#xff0c;如何回答HR提出的问题很大程度上决定了面试能不能成功。 下面是软件测试人员在面试过程中经常被问到的6个问题&#xff0c;告诉你怎么回答才不会被面试官套路.. 01、请你做一个自我介绍 误区&#xff1a; 一般人回答这个问题过于平常&#xff0c;只…