[Android] Input事件分发流程之IMS初始化(1)

IMS初始化

一、简介

当用户触摸屏幕或者按键操作,首次触发的是硬件驱动,驱动收到事件后,将该相应事件写入到输入设备节点, 这便产生了最原生态的内核事件。接着,输入系统取出原生态的事件,经过层层封装后成为KeyEvent或者MotionEvent ;最后,交付给相应的目标窗口(Window)来消费该输入事件

二、对象介绍

  1. frameworks/native/services/inputflinger/reader/InputReader.cpp
  2. frameworks/native/services/inputflinger/reader/EventHub.cpp
  3. frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
  4. frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
  5. frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
  6. frameworks/native/services/inputflinger/InputManager.cpp

三**、IMS初始化分析**

InputManagerService位于SystemServer.java#startOtherServices方法中被启动,源码如下

private void startOtherServices(@NonNull TimingsTraceAndSlog t) {...InputManagerService inputManager = null;...t.traceBegin("StartInputManagerService");inputManager = new InputManagerService(context);t.traceEnd();...// WMS与:InputManagerService/ActivityTaskManager/PhoneWindowManager相互绑定// WMS创建时会基于DisplayThread,也就是WMS是运行在android.display线程中wm = WindowManagerService.main(context, **inputManager**, !mFirstBoot, mOnlyCore,new PhoneWindowManager(), mActivityManagerService.mActivityTaskManager);// 将IMS加入ServiceManager中统一管理ServiceManager.addService(Context.INPUT_SERVICE, inputManager);...inputManager.start();

所以IMS的执行顺序为:new InputManagerService(context);inputManager.start();并且属于systemServer线程,继续看构造方法

frameworks/base/services/core/java/com/android/server/input/InputManagerService.java

public InputManagerService(Context context) {this.mContext = context;// 运行在android.display线程,这个handler很重要this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());...//初始化native对象mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());...LocalServices.addService(InputManagerInternal.class, new LocalService());}

this.mHandler是非常重要的,运行在android.display线程上,跟WMS所处的线程一样,主要用于以下几个方面:

  1. 接收和处理输入事件:mHandler会接收来自底层硬件(如触摸屏、按键等)的原始输入事件,并将其封装成消息发送给mHandler处理。这样可以将输入事件的处理从主线程转移到后台线程(android.display)
  2. **分发输入事件:**mHandler会将接收到的输入事件分发给相应的窗口或应用程序进行处理。通过消息的处理和分发机制,确保输入事件能够顺利传递给正确的目标,实现用户输入的精确响应。
  3. **处理输入事件队列:**mHandler会维护一个输入事件队列,将接收到的输入事件按照一定的顺序存储在队列中,并逐个处理。这样可以保证输入事件的有序性,避免输入事件的丢失或混乱。

总而言之,mHandler在InputManagerService中的作用是接收、处理和分发输入事件,实现用户输入的响应和传递。通过将输入事件的处理从主线程转移到android.display线程,systemServer线程会等待display线程处理完成后才会继续执行

继续看看nativeInit方法,位于:frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp

类名叫:NativeInputManager

static jlong nativeInit(JNIEnv* env, jclass /* clazz */,jobject serviceObj/*IMS*/, jobject contextObj/*context*/, jobject messageQueueObj/*MessageQueue*/) {// messageQueueObj就是上层调用的:mHandler.getLooper().getQueue()// 该handler属于android.display线程// 这段代码定义了一个名为sp的变量,它的类型是一个智能指针,指向一个MessageQueue对象。智能指针是一种封装了指针的对象,// 它具有自动管理内存的能力,即在不需要使用该指针时会自动释放它所指向的内存空间,// 避免了内存泄漏的问题。这里使用的是Android系统提供的sp智能指针,它是一个引用计数智能指针,可以在多个线程间安全地传递和共享// 这里获取了Message队列sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);if (messageQueue == nullptr) {jniThrowRuntimeException(env, "MessageQueue is not initialized.");return 0;}//NativeInputManager中创建了InputManagerNativeInputManager* im = new NativeInputManager(contextObj, serviceObj,messageQueue->getLooper());im->incStrong(0);return reinterpret_cast<jlong>(im);
}

先介绍一下方法里的形参:

  • JNIEnv* env:JNI对象,native方法必须也有的,该参数无需开发者去关心(只有JNI开发时会用到里面的参数和方法,此处不做关心)
  • serviceObj:InputManagerService.java的实例对象
  • contextObj:IMS中的context上下文
  • messageQueueObj:mHandler中Looper里管理的队列

以上代码只关注:获取了messageQueue和创建了NativeInputManager对象

NativeInputManager::NativeInputManager(jobject contextObj,jobject serviceObj, const sp<Looper>& looper/*messageQueue的looper*/) :mLooper(looper), mInteractive(true) {JNIEnv* env = jniEnv();// mServiceObj就是InputManagerServicemServiceObj = env->NewGlobalRef(serviceObj);{AutoMutex _l(mLock);mLocked.systemUiLightsOut = false;mLocked.pointerSpeed = 0;mLocked.pointerGesturesEnabled = true;mLocked.showTouches = false;mLocked.pointerDisplayId = ADISPLAY_ID_DEFAULT;}mInteractive = true;// 初始化InputManager,创建InputManager对象InputManager* im = new InputManager(this, this);mInputManager = im;defaultServiceManager()->addService(String16("inputflinger"), im);
}

然后继续创建了InputManager对象,并将im加入服务队列中,继续看看IM对象的构造方法

frameworks/native/services/inputflinger/InputManager.cpp

InputManager::InputManager(const sp<InputReaderPolicyInterface>& readerPolicy,const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {// 创建InputDispatcher// 创建InputReader// InputReader读取/dev/input节点,然后将数据传递给InputDispatcher// 由InputDispatcher来分发事件到window// readerPolicy和dispatcherPolicy都是NativeInputManager对象,NativeInputManager实现了这两个接口// 就饿可以通过Policy来回调数据给NativeInputManager了mDispatcher = createInputDispatcher(dispatcherPolicy);mClassifier = new InputClassifier(mDispatcher);// 包含了EventHub和InputListenerInterface及readerPolicymReader = createInputReader(readerPolicy, mClassifier);
}// 创建InputReader,并创建了EventHub
sp<InputReaderInterface> createInputReader(const sp<InputReaderPolicyInterface>& policy,const sp<InputListenerInterface>& listener) {return new InputReader(std::make_unique<EventHub>(), policy, listener);
}// 创建InputDispatcher
sp<InputDispatcherInterface> createInputDispatcher(const sp<InputDispatcherPolicyInterface>& policy) {return new android::inputdispatcher::InputDispatcher(policy);
}

1.记住两个重要的接口InputReaderPolicyInterfaceInputDispatcherPolicyInterface,他们的具体实现在NativeInputManager中,也就是com_android_server_input_InputManagerService.cpp中。

2.然后创建了InputDispatcherInputReader,并在InputReader创建过程中同步创建了EventHub

3.InputReader:读取/dev/input节点,然后将数据传递给InputDispatcher

4.InputDispatcher:接收来自InputReader的事件,然后由InputDispatcher来分发事件到window

继续看看EventHub的构造方法,看看有什么作用

frameworks/native/services/inputflinger/reader/EventHub.cpp

EventHub::EventHub(void): mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD),mNextDeviceId(1),mControllerNumbers(),mNeedToSendFinishedDeviceScan(false),mNeedToReopenDevices(false),mNeedToScanDevices(true),mPendingEventCount(0),mPendingEventIndex(0),mPendingINotify(false) {ensureProcessCanBlockSuspend();mEpollFd = epoll_create1(EPOLL_CLOEXEC);LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));// inotify是Linux内核提供的一种文件系统监控机制,它可以监测文件或目录的变化,并通过特定的事件通知机制通知进程mINotifyFd = inotify_init();// 此处DEVICE_PATH为"/dev/input",监听该设备节点创建与删除操作// 屏幕的所有事件都会经过处理存放进/dev/input,内核态mInputWd = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);LOG_ALWAYS_FATAL_IF(mInputWd < 0, "Could not register INotify for %s: %s", DEVICE_PATH,strerror(errno));if (isV4lScanningEnabled()) {mVideoWd = inotify_add_watch(mINotifyFd, VIDEO_DEVICE_PATH, IN_DELETE | IN_CREATE);LOG_ALWAYS_FATAL_IF(mVideoWd < 0, "Could not register INotify for %s: %s",VIDEO_DEVICE_PATH, strerror(errno));} else {mVideoWd = -1;ALOGI("Video device scanning disabled");}struct epoll_event eventItem = {};eventItem.events = EPOLLIN | EPOLLWAKEUP;eventItem.data.fd = mINotifyFd;// 将mINotifyFd添加到fd池中int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);LOG_ALWAYS_FATAL_IF(result != 0, "Could not add INotify to epoll instance.  errno=%d", errno);int wakeFds[2];// 调用了 pipe() 函数来创建一个无名管道,并将管道的读取端口和写入端口的文件描述符分别存储在 wakeFds[0] 和 wakeFds[1] 中。// 这个管道可以用于进程间通信,其中一个进程将数据写入管道,而另一个进程则可以从管道中读取这些数据。result = pipe(wakeFds);LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe.  errno=%d", errno);mWakeReadPipeFd = wakeFds[0];mWakeWritePipeFd = wakeFds[1];// 将pipe的读设置为非阻塞方式result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking.  errno=%d",errno);// 将pipe的写设置为非阻塞方式result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking.  errno=%d",errno);eventItem.data.fd = mWakeReadPipeFd;// 添加管道的读端到epoll实例result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance.  errno=%d",errno);
}

作用总结:创建文件系统监控机制,它可以监测/dev/input的变化(因为从屏幕触碰的事件都会存入这个驱动节点中),创建管道用于进程间通信(文件描述符存放到wakeFds[0] 和 wakeFds[1]中),其中一个进程将数据写入管道,而另一个进程则可以从管道中读取这些数据, 再将pipe的读/写设置为非阻塞方式。

创建监控驱动节点变化的文件描述符,通过监听驱动节点的内容,将内容放入管道,进行两端通信

frameworks/native/services/inputflinger/reader/InputReader.cpp

InputReader::InputReader(std::shared_ptr<EventHubInterface> eventHub/*eventHub*/,const sp<InputReaderPolicyInterface>& policy/*NativeInputManager*/,const sp<InputListenerInterface>& listener): mContext(this),mEventHub(eventHub),mPolicy(policy)...{// 创建了QueuedInputListener--->创建输入监听对象// 此处mQueuedListener的成员变量mInnerListener便是InputDispatcher对象。mQueuedListener = new QueuedInputListener(listener);...
}QueuedInputListener::QueuedInputListener(const sp<InputListenerInterface>& innerListener) :mInnerListener(innerListener) {
}

创建了QueuedInputListener,里面有个成员变量mInnerListener(InputListenerInterface),指向的回调就是InputDispatcher对象,InputListenerInterface接口很重要,用于将输入事件封装成NotifyKeyArgs对象回调给InputDispatcher

再来看看rameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp

InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy): mPolicy(policy),mPendingEvent(nullptr),mLastDropReason(DropReason::NOT_DROPPED),mIdGenerator(IdGenerator::Source::INPUT_DISPATCHER),mAppSwitchSawKeyDown(false),mAppSwitchDueTime(LONG_LONG_MAX),mNextUnblockedEvent(nullptr),mDispatchEnabled(false),mDispatchFrozen(false),mInputFilterEnabled(false),// mInTouchMode will be initialized by the WindowManager to the default device config.// To avoid leaking stack in case that call never comes, and for tests,// initialize it here anyways.mInTouchMode(true),mMaximumObscuringOpacityForTouch(1.0f),mFocusedDisplayId(ADISPLAY_ID_DEFAULT),mWindowTokenWithPointerCapture(nullptr),mLatencyAggregator(),mLatencyTracker(&mLatencyAggregator),mCompatService(getCompatService()) {// 创建一个loopermLooper = new Looper(false);mReporter = createInputReporter();mKeyRepeatState.lastKeyEntry = nullptr;// 获取分发超时参数policy->getDispatcherConfiguration(&mConfig);
}
  1. 创建属于自己线程的Looper对象;
  2. 配置超时机制,超时参数来自于IMS,参数默认值keyRepeatTimeout= 500,keyRepeatDelay = 50。
  3. keyRepeatTimeout的值为500 * 1000000LL,即500毫秒。这个值表示当用户按住一个键不放时,系统会在这个键按下后的500毫秒之后开始重复这个键的事件
  4. keyRepeatDelay的值为50 * 1000000LL,即50毫秒。这个值表示当用户按住一个键不放时,系统会在这个键按下后的50毫秒之后开始发送第一个重复事件

初始化就算完成了,再来总结一下:首先通过上层的IMS进入native层,创建NativeInputManager—>InputManager,然后创建了EventHub,InputReader,InputDispatcher,EventHub用于创建文件描述符来监听/dev/input驱动节点的内容,再通过管道形式进行数据传递,InputReader创建了事件输入的监听器(QueuedInputListener),InputDispatcher创建了属于自己的looper和配置了超时机制。以上的输入事件均运行在android.display线程,因为looper是由IMS传递下来的在这里插入代码片

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/96977.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

acwing算法基础之基础算法--高精度除法算法

目录 1 知识点2 模板 1 知识点 大数除以小数&#xff0c;返回商和余数。 2 模板 //A是大数&#xff0c;低位在前 //b是小数 //C是商&#xff0c;低位在前 //r是余数 vector<int> div(vector<int> &A, int b, int &r) {vector<int> C;for (int i …

CSS文本超出显示小数点

目录 1、单行文本溢出 2、多行文本溢出 1、基于高度截断 2、基于行数截断 1、单行文本溢出 如果解决文本溢出显示省略号&#xff0c;需要满足的三个条件&#xff1a; 先强制一行内显示文本 white-space:nowrap;/*默认normal 自动换行*/ 超出的文本隐藏起来。 overflow:…

clickonce 发布的winform 如何CA认证?

要为使用ClickOnce发布的WinForms应用程序启用CA&#xff08;证书颁发机构&#xff09;认证&#xff0c;您可以按照以下步骤进行操作&#xff1a; 1. **获取数字证书**&#xff1a; - 首先&#xff0c;您需要获得一个数字证书&#xff0c;通常从受信任的CA购买。这个数字证…

unity脚本_生命周期函数 c#

帧&#xff1a;fps 即每秒钟跑的游戏帧数 游戏的本质 是一个死循环 每一次循环处理游戏逻辑就会更新一次画面 之所以能看到画面在动 是因为切换画面的速度达到一定时人眼就认为画面时流畅的 一帧就是执行一次循环 人眼舒适放松时可视帧数 24帧/s 游戏卡顿的原因&#xff1a; …

【将文本编码为图像灰度级别】以 ASCII 编码并与灰度级别位混合将文本字符串隐藏到图像像素的最低位中,使其不明显研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

土木硕设计院在职转码上岸

一、个人介绍 双非土木硕&#xff0c;98年&#xff0c;目前在北京&#xff0c;职位为前端开发工程师&#xff0c;设计院在职期间自学转码上岸&#x1f33f; 二、背景 本人于19年开始土木研究生生涯&#xff0c;研二期间去地产实习近半年(碧桂园和世茂&#xff0c;这两家的地产…

跨考408的C语言需要什么水平?

跨考408的C语言需要什么水平? 其实C语言了解一下就可以了&#xff0c;复习之前可以在b站上面随便找个视频看一下&#xff0c;指针部分重点学习一下就 行&#xff0c;C语言主要是数据结构代码部分的基础&#xff0c;对于跨考生来说&#xff0c;先看一下C语言对数据结构的复习有…

MySQL面试题合集

MySQL面经知识整理 文章目录 MySQL面经知识整理一、查询相关1.什么是MySQL的连接查询&#xff0c;左连接&#xff0c;右连接&#xff0c;内外连接2.SQL慢查询优化的方法3.大表查询如何优化 二、索引相关1.在MySQL中,可以通过哪些命令来查看查询是否使用了索引2.MySQL的最左匹配…

跨域请求方案整理实践

项目场景&#xff1a; 调用接口进行手机验证提示,项目需要调用其它域名的接口,导致前端提示跨域问题 问题描述 前端调用其他域名接口时报错提示: index.html#/StatisticalAnalysisOfVacancy:1 Access to XMLHttpRequest at http://xxxxx/CustomerService/template/examineMes…

#力扣:13. 罗马数字转整数@FDDLC

13. 罗马数字转整数 一、Java import java.util.HashMap;class Solution {public int romanToInt(String s) {HashMap<Character, Integer> m new HashMap<>() {{put(I, 1);put(V, 5);put(X, 10);put(L, 50);put(C, 100);put(D, 500);put(M, 1000);}};char[] a …

背包问题学习笔记-分组背包

题意描述&#xff1a; 有 N 组物品和一个容量是 V 的背包。每组物品有若干个&#xff0c;同一组内的物品最多只能选一个。每件物品的体积是 vij&#xff0c;价值是 wij&#xff0c;其中 i 是组号&#xff0c;j 是组内编号。求解将哪些物品装入背包&#xff0c;可使物品总体积不…

UniApp创建项目HelloWorld

浏览器预览效果镇楼 普通项目创建 点击创建完成后&#xff0c;就如下所示 确实和微信小程序开发差不多。只是稍微换了一个名字的概念了&#xff0c;这个就是开发嘛&#xff0c;不要过于纠结概念性东西。开发开发&#xff0c;开了就知道怎么发了&#xff1f; 或许是 反正write就…

基于虚拟阻抗的下垂控制——孤岛双机并联Simulink仿真

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Python3操作文件系列(二):文件数据读写|二进制数据读写

Python3操作文件系列(一):判断文件|目录是否存在三种方式 Python3操作文件系列(二):文件数据读写|二进制数据读写 Python3数据文件读取与写入 一: 文件数据|二进制数据读写 import os"""Python3的open(file,mode"文件的操作模式")利用该函数可以对…

基于腾讯云的OTA远程升级

一、OTA OTA即over the air,是一种远程固件升级技术&#xff0c;它允许在设备已经部署在现场运行时通过网络远程更新其固件或软件。OTA技术有许多优点&#xff0c;比如我们手机系统有个地方做了优化&#xff0c;使用OTA技术我们就不用召回每部手机&#xff0c;直接通过云端就可…

Git 学习笔记 | 版本控制和版本控制工具

Git 学习笔记 | 版本控制和版本控制工具 Git 学习笔记 | 版本控制和版本控制工具什么是版本控制&#xff1f;版本管理工具的特性版本管理工具的发展简史主流的版本控制器本地版本控制集中版本控制分布式版本控制 Git与SVN的主要区别 Git 学习笔记 | 版本控制和版本控制工具 学…

HttpClient实现爬虫开发

网络爬虫是一种高效获取网络信息的方式&#xff0c;而HttpClient是一个强大而灵活的Java库&#xff0c;提供了方便的API和丰富的功能&#xff0c;使其成为开发高效且灵活的网络爬虫的理想选择。本文将分享如何利用HttpClient库进行网络爬虫开发&#xff0c;帮助您更好地理解并实…

【计算机网络】因特网中的电子邮件

文章目录 简单邮件传送协议SMTP邮件访问协议POP3IMAPHTTP 参考资料 电子邮件为异步通信媒介 因特网电子邮件系统 电子邮件系统的三个构件&#xff1a;用户代理、邮件服务器、邮件发送和读取协议 用户代理 User Agent 即UA 电子邮件客户端软件&#xff0c;用户与电子邮件系统的接…

林沛满-TCP之在途字节数

本文整理自&#xff1a;《Wireshark网络分析的艺术 第1版》 作者&#xff1a;林沛满 著 出版时间&#xff1a;2016-02 我一直谨记斯蒂芬霍金的金玉良言—每写一道数学公式就会失去一半读者。不过为了深度分析网络包&#xff0c;有时候是不得不计算的&#xff0c;好在小学一年级…

图形学中一些基本知识的总结与复习

前言 在过完games101课程后仍然觉得自己还有许多地方不懂与遗漏&#xff0c;以此来补充与复习一些其中的知识。 参考&#xff1a;Games101、《Unity Shader 入门精要》 GPU渲染流水线(GPU Rendering Pipeline) ----注&#xff1a;Games101课程中所展示渲染流程与书中有所不同&…