和你一起终身学习,这里是程序员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 的结构图

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 对进行事件分发。

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

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

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 开发干货集锦

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

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

InputManagerService启动流程分析相关推荐

  1. 解析并符号 读取dll_Spring IOC容器之XmlBeanFactory启动流程分析和源码解析

    一. 前言 Spring容器主要分为两类BeanFactory和ApplicationContext,后者是基于前者的功能扩展,也就是一个基础容器和一个高级容器的区别.本篇就以BeanFactory基 ...

  2. Zygote进程启动流程分析

    文中的源代码版本为api23 Zygote进程启动流程分析 先说结论,zygote进程启动过程中主要做了下面这些事情: 启动DVM虚拟机 预加载部分资源,如一些通用类.通用资源.共享库等 启动syst ...

  3. c++builder启动了怎么停止_App 竟然是这样跑起来的 —— Android App/Activity 启动流程分析...

    在我的上一篇文章: AJie:按下电源键后竟然发生了这一幕 -- Android 系统启动流程分析​zhuanlan.zhihu.com 我们分析了系统在开机以后的一系列行为,其中最后一阶段 AMS( ...

  4. SpringBoot启动流程分析(四):IoC容器的初始化过程

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  5. Exynos4412 Uboot 移植(二)—— Uboot 启动流程分析

    uboot启动流程分析如下: 第一阶段: a -- 设置cpu工作模式为SVC模式 b -- 关闭中断,mmu,cache v -- 关看门狗 d -- 初始化内存,串口 e -- 设置栈 f -- ...

  6. bootloader启动流程分析

    bootloader启动流程分析 1.Bootloader的概念和作用 Bootloader是嵌入式系统的引导加载程序,它是系统上电后运行的第一段程序.在完成对系统的初始化任务之后,它会将Flash中 ...

  7. MyBatis启动流程分析

    目录 MyBatis简单介绍 启动流程分析 简单总结 附录 MyBatis内置别名转换 参考 MyBatis简单介绍 MyBatis是一个持久层框架,使用简单,学习成本较低.可以执行自己手写的SQL语 ...

  8. NameNode之启动流程分析

    NameNode启动流程分析 public staticvoid main(Stringargv[]) throws Exception { if (DFSUtil.parseHelpArgument ...

  9. springboot中获得app_Spring Boot 应用程序启动流程分析

    SpringBoot 有两个关键元素: @SpringBootApplication SpringApplication 以及 run() 方法 SpringApplication 这个类应该算是 S ...

  10. GEF入门实例_总结_04_Eclipse插件启动流程分析

    一.前言 本文承接上一节:GEF入门实例_总结_03_显示菜单和工具栏 注意到app目录下的6个类文件. 这6个文件对RCP应用程序而言非常重要,可能我们现在对这几个文件的理解还是云里雾里,这一节我们 ...

最新文章

  1. 开源Math.NET基础数学类库使用(13)C#实现其他随机数生成器
  2. WordPress 性能优化:为什么我的博客比你的快
  3. python输入一个人的名字_怎样用c语言做到输入一个人的名字才会输出一个心?
  4. imu_utils标定imu问题解决
  5. html ie乱码_Java 0基础入门(初识Html)
  6. python封装模块_Python练手,封装日志模块,v2
  7. 打开word2007总是出现配置进度_实战经验:Word 2007每次打开都弹出正在配置
  8. MySQL 的CASE WHEN 语句
  9. 教你如何在STM32中使用DSP指令
  10. 二叉搜索树c++_LeetCode98验证二叉搜索树
  11. 大数据-数据仓库的概念
  12. xrd连续扫描和步进扫描_XRD步进扫描方式和连续扫描方式选择
  13. erdas几何校正_erdas图像几何校正操作步骤指南
  14. java xml生成word文档_java生成word文档
  15. MATLAB中图像索引和抖动转换
  16. WordPress 搭建超级好看的主题博客
  17. 为什么要学数学?因为它真的没用啊!
  18. 为什么总是闹离职的员工没走,平时不吭声的员工却突然离职?
  19. 学校计算机及设备维护维修管理制度,《计算机设备维护维修管理制度.doc
  20. php优势和技术应用

热门文章

  1. 中国市场主流商业智能工具的对比
  2. 开始使用 Elasticsearch (3)
  3. 用“创业与投资”类比来分析《西部世界》第一、二季的架构
  4. 安卓获取设备的系统类型,系统版本,手机生产厂商和手机型号
  5. toefl 备考2.17
  6. python 爬虫爬出来为什么都是空的_为什么铺天盖地都是python的广告?
  7. php支付宝第三方授权,原生PHP实现支付宝App第三方登录获取用户信息
  8. JAVA 文件下载复制百分比进度计算公式
  9. pytorch基于Unet的铁轨缺陷语义分割
  10. ryuyan 方差分析_R语言入门之方差分析(ANOVA)