InputManagerService启动流程分析

2023-11-06 04:00

本文主要是介绍InputManagerService启动流程分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

cd13bc375091e717a167c3f17a1d399f.gif

和你一起终身学习,这里是程序员Android

经典好文推荐,通过阅读本文,您将收获以下知识点:

一、前言
二、启动流程
2.1 创建输入系统
2.2 启动输入系统
2.3 输入系统就绪

一、前言

之前写过几篇关于输入系统的文章,但是还没有写完,后来由于工作的变动,这个事情就一直耽搁了。而现在,在工作中,遇到输入系统相关的事情也越来越多,其中有一个非常有意思的需求,因此是时候继续分析 InputManagerService。

InputManagerService 系统文章,基于 Android 12 进行分析。

本文将以 IMS 简称 InputManagerService。

二、启动流程

InputManagerService 是一个系统服务,启动流程如下

// SystemServer.javaprivate void startOtherServices(@NonNull TimingsTraceAndSlog t) {// ..// 1. 创建inputManager = new InputManagerService(context);// 注册服务    ServiceManager.addService(Context.INPUT_SERVICE, inputManager,/* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL);// 保存 wms 的回调inputManager.setWindowManagerCallbacks(wm.getInputManagerCallback());// 2. 启动inputManager.start();    try {// 3. 就绪if (inputManagerF != null) {inputManagerF.systemRunning();}} catch (Throwable e) {reportWtf("Notifying InputManagerService running", e);}// ...
}

IMS 的启动流程分为三步

1.创建输入系统,建立上层与底层的映射关系。
2.启动输入系统,其实就是启动底层输入系统的几个模块。
3.输入系统就绪,上层会同步一些配置给底层输入系统。

下面分三个模块,分别讲解这三步。

2.1 创建输入系统
// InputManagerService.javapublic InputManagerService(Context context) {this.mContext = context;this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());// 配置为空mStaticAssociations = loadStaticInputPortAssociations();// 默认 falsemUseDevInputEventForAudioJack =context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);// 1. 底层进行初始化// mPtr 指向底层创建的 NativeInputManager 对象mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());// 空String doubleTouchGestureEnablePath = context.getResources().getString(R.string.config_doubleTouchGestureEnableFile);// nullmDoubleTouchGestureEnableFile = TextUtils.isEmpty(doubleTouchGestureEnablePath) ? null :new File(doubleTouchGestureEnablePath);LocalServices.addService(InputManagerInternal.class, new LocalService());
}

IMS 构造函数,主要就是调用 nativeInit() 来初始化底层输入系统。

// com_android_server_input_InputManagerService.cppstatic jlong nativeInit(JNIEnv* env, jclass /* clazz */,jobject serviceObj, jobject contextObj, jobject messageQueueObj) {// 从Java层的MessageQueue中获取底层映射的MessageQueuesp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);if (messageQueue == nullptr) {jniThrowRuntimeException(env, "MessageQueue is not initialized.");return 0;}// 创建 NativeInputManagerNativeInputManager* im = new NativeInputManager(contextObj, serviceObj,messageQueue->getLooper());im->incStrong(0);// 返回指向 NativeInputManager 对象的指针return reinterpret_cast<jlong>(im);
}

原来底层创建了 NativeInputManager 对象,然后返回给上层。

但是 NativeInputManager 并不是底层输入系统的服务,它只是一个连接上层输入系统和底层输入系统的桥梁而已。来看下它的创建过程

// com_android_server_input_InputManagerService.cppNativeInputManager::NativeInputManager(jobject contextObj,jobject serviceObj, const sp<Looper>& looper) :mLooper(looper), mInteractive(true) {JNIEnv* env = jniEnv();// 1.保存上层的InputManagerService对象mServiceObj = env->NewGlobalRef(serviceObj);// 2. 初始化一些参数{AutoMutex _l(mLock);// mLocked 的类型是 struct Locked,这里初始化了一些参数// 这些参数会被上层改变mLocked.systemUiVisibility = ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE;mLocked.pointerSpeed = 0;mLocked.pointerGesturesEnabled = true;mLocked.showTouches = false;mLocked.pointerCapture = false;mLocked.pointerDisplayId = ADISPLAY_ID_DEFAULT;}mInteractive = true;// 3.创建并注册服务 InputManagermInputManager = new InputManager(this, this);defaultServiceManager()->addService(String16("inputflinger"),mInputManager, false);
}

NativeInputManager 构造过程如下

1.创建一个全局引用,并通过 mServiceObj 指向上层的 InputManagerService 对象。
2.初始化参数。这里要注意一个结构体变量 mLocked,它的一些参数都是由上层控制的。例如,mLocked.showTouches 是由开发者选项中 "Show taps" 决定的,它的功能是在屏幕上显示一个触摸点。
3.创建并注册服务 InputManager。

原来,InputManager 才是底层输入系统的服务,而 NativeInputManagerService 通过 mServiceObj 保存了上层 InputManagerService 引用,并且上层 InputManagerService 通过 mPtr 指向底层的 NativeInputManager。因此,我们可以判定 NativeInputManager 就是一个连接上层与底层的桥梁。

我们注意到创建 InputManager 使用了两个 this 参数,这里介绍下 NativeInputManager 和 InputManager 的结构图

outside_default.png

InputManager 构造函数需要的两个接口正好是由 NativeInputManager 实现的,然而,具体使用这两个接口的不是 InputManager,而是它的子模块。这些子模块都是在 InputManager 的构造函数中创建的

// InputManager.cpp
InputManager::InputManager(const sp<InputReaderPolicyInterface>& readerPolicy,const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {// 1. 创建InputDispatcher对象,使用 InputDispatcherPolicyInterface 接口mDispatcher = createInputDispatcher(dispatcherPolicy);// 2. 创建InputClassifier对象,使用 InputListenerInterfacemClassifier = new InputClassifier(mDispatcher);// 3. 创建InputReader对象,使用 InputReaderPolicyInterface 和 InputListenerInterfacemReader = createInputReader(readerPolicy, mClassifier);
}// InputDispatcherFactory.cpp
sp<InputDispatcherInterface> createInputDispatcher(const sp<InputDispatcherPolicyInterface>& policy) {return new android::inputdispatcher::InputDispatcher(policy);
}// InputReaderFactory.cpp
sp<InputReaderInterface> createInputReader(const sp<InputReaderPolicyInterface>& policy,const sp<InputListenerInterface>& listener) {return new InputReader(std::make_unique<EventHub>(), policy, listener);
}

InputManager 构造函数所使用的两个接口,分别由 InputDispatcher 和 InputReader 所使用。因此 InputManager 向上通信的能力是由子模块 InputDispatcher 和 InputReader 实现的。

InputManager 创建了三个模块,InputReader、InputClassifier、InputDispatcher。 InputReader 负责从 EventHub 中获取事件,然后把事件加工后,发送给 InputClassfier。InputClassifer 会把事件发送给 InputDispatcher,但是它会对触摸事件进行一个分类工作。最后 InputDispatcher 对进行事件分发。

那么现在我们可以大致推算下输入系统的关系图,如下

outside_default.png

这个关系图很好的体现了设计模式的单一职责原则。

EventHub 其实只属于 InputReader,因此要想解剖整个输入系统,我们得逐一解剖 InputReader、InputClassifier、InputDispatcher。后面的一系列的文章将逐个来剖析。

2.2 启动输入系统
// InputManagerService.javapublic void start() {Slog.i(TAG, "Starting input manager");// 1.启动native层nativeStart(mPtr);// Add ourself to the Watchdog monitors.Watchdog.getInstance().addMonitor(this);// 2.监听数据库,当值发生改变时,通过 native 层// 监听Settings.System.POINTER_SPEED,这个表示手指的速度registerPointerSpeedSettingObserver();// 监听Settings.System.SHOW_TOUCHES,这个表示是否在屏幕上显示触摸坐标registerShowTouchesSettingObserver();// 监听Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICONregisterAccessibilityLargePointerSettingObserver();// 监听Settings.Secure.LONG_PRESS_TIMEOUT,这个多少毫秒触发长按事件registerLongPressTimeoutObserver();// 监听用户切换mContext.registerReceiver(new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {updatePointerSpeedFromSettings();updateShowTouchesFromSettings();updateAccessibilityLargePointerFromSettings();updateDeepPressStatusFromSettings("user switched");}}, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler);// 3. 从数据库获取值,并传递给 native 层updatePointerSpeedFromSettings();updateShowTouchesFromSettings();updateAccessibilityLargePointerFromSettings();updateDeepPressStatusFromSettings("just booted");}

输入系统的启动过程如下

1.启动底层输入系统。其实就是启动刚刚说到的 InputReader, InputDispatcher。
2.监听一些广播。因为这些广播与输入系统的配置有关,当接收到这些广播,会更新配置到底层。
3.直接读取配置,更新到底层输入系统。

第2步和第3步,本质上其实都是更新配置到底层,但是需要我们对 InputReader 的运行过程比较熟悉,因此这个配置更新过程,留到后面分析。

现在我们直接看下如何启动底层的输入系统

// com_android_server_input_InputManagerService.cppstatic void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);// 调用InputManager::start()status_t result = im->getInputManager()->start();if (result) {jniThrowRuntimeException(env, "Input manager could not be started.");}
}

通过 JNI 层的 NativeInputManager 这个桥梁来启动 InputManager。

前面用一幅图表明了 NativeInputManager 的桥梁作用,现在感受到了吗?

status_t InputManager::start() {// 启动 Dispatcherstatus_t result = mDispatcher->start();if (result) {ALOGE("Could not start InputDispatcher thread due to error %d.", result);return result;}// 启动 InputReaderresult = mReader->start();if (result) {ALOGE("Could not start InputReader due to error %d.", result);mDispatcher->stop();return result;}return OK;
}

InputManager 的启动过程很简单,就是直接启动它的子模块 InputDispatcher 和 InputReader。

InputDispatcher 和 InputReader 的启动,都是通过 InputThread 创建一个线程来执行任务。

//InputThread.cppInputThread::InputThread(std::string name, std::function<void()> loop, std::function<void()> wake): mName(name), mThreadWake(wake) {mThread = new InputThreadImpl(loop);mThread->run(mName.c_str(), ANDROID_PRIORITY_URGENT_DISPLAY);
}

注意 InputThread 可不是一个线程,InputThreadImpl 才是一个线程,如下

//InputThread.cppclass InputThreadImpl : public Thread {
public:explicit InputThreadImpl(std::function<void()> loop): Thread(/* canCallJava */ true), mThreadLoop(loop) {}~InputThreadImpl() {}private:std::function<void()> mThreadLoop;bool threadLoop() override {mThreadLoop();return true;}
};

当线程启动后,会循环调用 threadLoop(),直到这个函数返回 false。从 InputThreadImpl 的定义可以看出,threadLoop() 会一直保持循环,并且每一次循环,会调用一次 mThreadLoop(),而函数 mThreadLoop 是由 InputReader 和 InputDispacher 在启动时传入

// InputReader.cpp
status_t InputReader::start() {if (mThread) {return ALREADY_EXISTS;}// 线程启动后,循环调用 loopOnce()mThread = std::make_unique<InputThread>("InputReader", [this]() { loopOnce(); }, [this]() { mEventHub->wake(); });return OK;
}// InputDispatcher.cpp
status_t InputDispatcher::start() {if (mThread) {return ALREADY_EXISTS;}// 线程启动后,循环调用 dispatchOnce()mThread = std::make_unique<InputThread>("InputDispatcher", [this]() { dispatchOnce(); }, [this]() { mLooper->wake(); });return OK;
}

现在,我们可以明白,InputReader 启动时,会创建一个线程,然后循环调用 loopOnce() 函数,而 InputDispatcher 启动时,也会创建一个线程,然后循环调用 dispatchOnce()。

2.3 输入系统就绪
// InputManagerService.javapublic void systemRunning() {mNotificationManager = (NotificationManager)mContext.getSystemService(Context.NOTIFICATION_SERVICE);synchronized (mLidSwitchLock) {mSystemReady = true;// Send the initial lid switch state to any callback registered before the system was// ready.int switchState = getSwitchState(-1 /* deviceId */, InputDevice.SOURCE_ANY, SW_LID);for (int i = 0; i < mLidSwitchCallbacks.size(); i++) {LidSwitchCallback callback = mLidSwitchCallbacks.get(i);callback.notifyLidSwitchChanged(0 /* whenNanos */, switchState == KEY_STATE_UP);}}// 监听广播,通知底层加载键盘布局IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);filter.addAction(Intent.ACTION_PACKAGE_REMOVED);filter.addAction(Intent.ACTION_PACKAGE_CHANGED);filter.addAction(Intent.ACTION_PACKAGE_REPLACED);filter.addDataScheme("package");mContext.registerReceiver(new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {updateKeyboardLayouts();}}, filter, null, mHandler);// 监听广播,通知底层加载设备别名filter = new IntentFilter(BluetoothDevice.ACTION_ALIAS_CHANGED);mContext.registerReceiver(new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {reloadDeviceAliases();}}, filter, null, mHandler);// 直接通知一次底层加载键盘布局和加载设备别名mHandler.sendEmptyMessage(MSG_RELOAD_DEVICE_ALIASES);mHandler.sendEmptyMessage(MSG_UPDATE_KEYBOARD_LAYOUTS);if (mWiredAccessoryCallbacks != null) {mWiredAccessoryCallbacks.systemReady();}
}private void reloadKeyboardLayouts() {nativeReloadKeyboardLayouts(mPtr);
}private void reloadDeviceAliases() {nativeReloadDeviceAliases(mPtr);
}

无论是通知底层加载键盘布局,还是加载设备别名,其实都是让底层更新配置。与前面一样,更新配置的过程,留到后面分析。

作者:大胃粥
链接:https://juejin.cn/post/7161376731096432653

友情推荐:

Android 开发干货集锦

至此,本篇已结束。转载网络的文章,小编觉得很优秀,欢迎点击阅读原文,支持原创作者,如有侵权,恳请联系小编删除,欢迎您的建议与指正。同时期待您的关注,感谢您的阅读,谢谢!

18c888bba85eb60d2449552d057b4e0f.jpeg

点击阅读原文,为大佬点赞!

这篇关于InputManagerService启动流程分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/354354

相关文章

Security OAuth2 单点登录流程

单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自独立的软件系统,提供访问控制的属性。当拥有这项属性时,当用户登录时,就可以获取所有系统的访问权限,不用对每个单一系统都逐一登录。这项功能通常是以轻型目录访问协议(LDAP)来实现,在服务器上会将用户信息存储到LDAP数据库中。相同的,单一注销(single sign-off)就是指

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

MySQL数据库宕机,启动不起来,教你一招搞定!

作者介绍:老苏,10余年DBA工作运维经验,擅长Oracle、MySQL、PG、Mongodb数据库运维(如安装迁移,性能优化、故障应急处理等)公众号:老苏畅谈运维欢迎关注本人公众号,更多精彩与您分享。 MySQL数据库宕机,数据页损坏问题,启动不起来,该如何排查和解决,本文将为你说明具体的排查过程。 查看MySQL error日志 查看 MySQL error日志,排查哪个表(表空间

springboot3打包成war包,用tomcat8启动

1、在pom中,将打包类型改为war <packaging>war</packaging> 2、pom中排除SpringBoot内置的Tomcat容器并添加Tomcat依赖,用于编译和测试,         *依赖时一定设置 scope 为 provided (相当于 tomcat 依赖只在本地运行和测试的时候有效,         打包的时候会排除这个依赖)<scope>provided

内核启动时减少log的方式

内核引导选项 内核引导选项大体上可以分为两类:一类与设备无关、另一类与设备有关。与设备有关的引导选项多如牛毛,需要你自己阅读内核中的相应驱动程序源码以获取其能够接受的引导选项。比如,如果你想知道可以向 AHA1542 SCSI 驱动程序传递哪些引导选项,那么就查看 drivers/scsi/aha1542.c 文件,一般在前面 100 行注释里就可以找到所接受的引导选项说明。大多数选项是通过"_

用命令行的方式启动.netcore webapi

用命令行的方式启动.netcore web项目 进入指定的项目文件夹,比如我发布后的代码放在下面文件夹中 在此地址栏中输入“cmd”,打开命令提示符,进入到发布代码目录 命令行启动.netcore项目的命令为:  dotnet 项目启动文件.dll --urls="http://*:对外端口" --ip="本机ip" --port=项目内部端口 例: dotnet Imagine.M

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者

MOLE 2.5 分析分子通道和孔隙

软件介绍 生物大分子通道和孔隙在生物学中发挥着重要作用,例如在分子识别和酶底物特异性方面。 我们介绍了一种名为 MOLE 2.5 的高级软件工具,该工具旨在分析分子通道和孔隙。 与其他可用软件工具的基准测试表明,MOLE 2.5 相比更快、更强大、功能更丰富。作为一项新功能,MOLE 2.5 可以估算已识别通道的物理化学性质。 软件下载 https://pan.quark.cn/s/57

Linux服务器Java启动脚本

Linux服务器Java启动脚本 1、初版2、优化版本3、常用脚本仓库 本文章介绍了如何在Linux服务器上执行Java并启动jar包, 通常我们会使用nohup直接启动,但是还是需要手动停止然后再次启动, 那如何更优雅的在服务器上启动jar包呢,让我们一起探讨一下吧。 1、初版 第一个版本是常用的做法,直接使用nohup后台启动jar包, 并将日志输出到当前文件夹n