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

相关文章

MySQL中的LENGTH()函数用法详解与实例分析

《MySQL中的LENGTH()函数用法详解与实例分析》MySQLLENGTH()函数用于计算字符串的字节长度,区别于CHAR_LENGTH()的字符长度,适用于多字节字符集(如UTF-8)的数据验证... 目录1. LENGTH()函数的基本语法2. LENGTH()函数的返回值2.1 示例1:计算字符串

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Spring Security中用户名和密码的验证完整流程

《SpringSecurity中用户名和密码的验证完整流程》本文给大家介绍SpringSecurity中用户名和密码的验证完整流程,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定... 首先创建了一个UsernamePasswordAuthenticationTChina编程oken对象,这是S

MySQL中的表连接原理分析

《MySQL中的表连接原理分析》:本文主要介绍MySQL中的表连接原理分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、背景2、环境3、表连接原理【1】驱动表和被驱动表【2】内连接【3】外连接【4编程】嵌套循环连接【5】join buffer4、总结1、背景

python中Hash使用场景分析

《python中Hash使用场景分析》Python的hash()函数用于获取对象哈希值,常用于字典和集合,不可变类型可哈希,可变类型不可,常见算法包括除法、乘法、平方取中和随机数哈希,各有优缺点,需根... 目录python中的 Hash除法哈希算法乘法哈希算法平方取中法随机数哈希算法小结在Python中,

Java Stream的distinct去重原理分析

《JavaStream的distinct去重原理分析》Javastream中的distinct方法用于去除流中的重复元素,它返回一个包含过滤后唯一元素的新流,该方法会根据元素的hashcode和eq... 目录一、distinct 的基础用法与核心特性二、distinct 的底层实现原理1. 顺序流中的去重

Android ViewBinding使用流程

《AndroidViewBinding使用流程》AndroidViewBinding是Jetpack组件,替代findViewById,提供类型安全、空安全和编译时检查,代码简洁且性能优化,相比Da... 目录一、核心概念二、ViewBinding优点三、使用流程1. 启用 ViewBinding (模块级

关于MyISAM和InnoDB对比分析

《关于MyISAM和InnoDB对比分析》:本文主要介绍关于MyISAM和InnoDB对比分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录开篇:从交通规则看存储引擎选择理解存储引擎的基本概念技术原理对比1. 事务支持:ACID的守护者2. 锁机制:并发控制的艺

nginx启动命令和默认配置文件的使用

《nginx启动命令和默认配置文件的使用》:本文主要介绍nginx启动命令和默认配置文件的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录常见命令nginx.conf配置文件location匹配规则图片服务器总结常见命令# 默认配置文件启动./nginx