Android Input子系统:Input进程的创建,监听线程的启动

简介:
本文讲的是 Android Input子系统:Input进程的创建,监听线程的启动, 本文主要从系统源码的角度带你一步步了解Android Input子系统。

从我个人的理解来看,Android的Input系统其实就是系统级的事件处理、分发框架,它需要的功能模块大致有:事件读取、事件分类、事件分发。那么我们就从整个Input系统的输入源入手,了解事件是如何被输入到Input系统中的。

在看代码前我们先想一想,如果要我们设计一个事件分发框架的输入读取模块,要考虑到哪些子模块:

  • 事件生成模块(当用户对设备进行操作产生InputEvent,硬件产生中断将事件交给驱动,驱动交给内核,内核交给framework)
  • 事件监听模块(这里就很像设计一个服务器,为了及时响应来自客户端的请求,则需要启动一个线程监听)
  • 事件读取模块
  • 事件分发模块

那么现在我们最起码可以知道整个学习的起点了,就是Input系统中,负责监听的线程是谁,监听的过程中它们做了什么。 在开始之前,给大家分享一张我根据本文内容画的图:

Android Input子系统:Input进程的创建,监听线程的启动

InputManagerService初始化概览

首先,有几点共识我们都可以达成:

  • Android Framework层的Service(Java)都是由system_server进程创建的(由于没有fork,因此都运行在system_server进程中)
  • Service创建后就会交给运行在system_server进程中的ServiceManager管理。

因此对于InputManagerService的创建,我们可以在SystemServer的startOtherServices()方法中找到,该方法做了以下事情:

  • 创建InputManagerService对象
  • 将它交给ServiceManager管理
  • 将WindowManagerService的InputMonitor注册到InputManagerService中作为窗口响应事件后的回调
  • 完成以上工作后启动InputManagerService。 
 
  1. SystemServer.javastartOtherServices(){ 
  2.     …… 
  3.     inputManager = new InputManagerService(context); 
  4.     …… 
  5.     inputManager.setWindowManagerCallbacks(wm.getInputMonitor()); 
  6.     inputManager.start(); 
  7.     …… 

接下来我们就逐部分学习相应的处理。

InputManagerService对象的创建

创建InputManagerService对象时会完成以下工作:

  • 创建一个负责处理DisplayThread线程中的Message的Handler
  • 调用nativeInit初始化native层的InputManagerService,初始化的时候传入了DisplayThread的消息队列
  • 用mPtr保存native层的InputManagerService
  • 初始化完成后将Service添加到LocalServices,通过Map以键值对的形式存储 
 
  1. InputManagerService.javapublic InputManagerService(Context context) {    this.mContext = context;    this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper()); 
  2.  
  3.     mUseDevInputEventForAudioJack = 
  4.             context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack); 
  5.     Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack=" 
  6.             + mUseDevInputEventForAudioJack); 
  7.     mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue()); 
  8.  
  9.     LocalServices.addService(InputManagerInternal.class, new LocalService()); 

这里可能有人就会问了,为什么InputManagerService要和DisplayThread绑定在一起?大家不妨想想,InputEvent无论如何被获取、归类、分发,最终还是要被处理,也就意味着最终它的处理结果都要在UI上体现,那么InputManagerService自然要选择和UI亲近一些的线程在一起了。

但是问题又来了,应用都是运行在自己的主线程里的,难道InputManagerService要一个个绑定么,还是一个个轮询?这些做法都太过低效,那换个办法,可不可以和某个管理或非常亲近所有应用UI的线程绑定在一起呢?

答案是什么,我在这里先不说,大家可以利用自己的知识想想。

初始化native层的InputManagerService

在nativeInit函数中,将Java层的MessageQueue转换为native层的MessageQueue,然后再取出Looper用于NativeInputManager的初始化。可见这里的重头戏就是NativeInputManager的创建,这个过程做了以下事情:

  • 将Java层的Context和InputManagerService转换为native层的Context和InputManagerService存储在mContextObj和mServiceObj中
  • 初始化变量
  • 创建EventHub
  • 创建InputManager 
 
  1. com_android_server_input_InputManagerService.cpp 
  2.  
  3. NativeInputManager::NativeInputManager(jobject contextObj, 
  4.         jobject serviceObj, const sp<Looper>& looper) : 
  5.         mLooper(looper), mInteractive(true) { 
  6.     JNIEnv* env = jniEnv(); 
  7.  
  8.     mContextObj = env->NewGlobalRef(contextObj); 
  9.     mServiceObj = env->NewGlobalRef(serviceObj); 
  10.  
  11.     {        AutoMutex _l(mLock); 
  12.         mLocked.systemUiVisibility = ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE; 
  13.         mLocked.pointerSpeed = 0; 
  14.         mLocked.pointerGesturesEnabled = true
  15.         mLocked.showTouches = false
  16.     } 
  17.     mInteractive = true
  18.  
  19.     sp<EventHub> eventHub = new EventHub(); 
  20.     mInputManager = new InputManager(eventHub, this, this); 

EventHub

看到这里很多人就会想,EventHub是什么?取英语释义来看,它的意思是事件枢纽。我们在文章开头的时候也提到过,Input系统的事件来源于驱动/内核,那么我们可以猜测EventHub是处理来自驱动/内核的元事件的枢纽。接下来就在源码中验证我们的想法吧。

EventHub的创建过程中做了以下事情:

  • 创建mEpollFd用于监听是否有数据(有无事件)可读
  • 创建mINotifyFd将它注册到DEVICE_PATH(这里路径就是/dev/input)节点,并将它交给内核用于监听该设备节点的增删数据事件。那么只要有数据增删的事件到来,epoll_wait()就会返回,使得EventHub能收到来自系统的通知,并获取事件的详细信息
  • 调用epoll_ctl函数将mEpollFd和mINotifyFd注册到epoll中
  • 定义int wakeFd[2]作为事件传输管道的读写两端,并将读端注册到epoll中让mEpollFd监听 
 
  1. EventHub.cpp 
  2.  
  3. EventHub::EventHub(void) : 
  4.         mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD), mNextDeviceId(1), mControllerNumbers(), 
  5.         mOpeningDevices(0), mClosingDevices(0), 
  6.         mNeedToSendFinishedDeviceScan(false), 
  7.         mNeedToReopenDevices(false), mNeedToScanDevices(true), 
  8.         mPendingEventCount(0), mPendingEventIndex(0), mPendingINotify(false) { 
  9.     acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID); 
  10.  
  11.     mEpollFd = epoll_create(EPOLL_SIZE_HINT); 
  12.     LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance.  errno=%d", errno); 
  13.  
  14.     mINotifyFd = inotify_init(); 
  15.     int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE); 
  16.     …… 
  17.     result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem); 
  18.     …… 
  19.     int wakeFds[2]; 
  20.     result = pipe(wakeFds); 
  21.     …… 
  22.     mWakeReadPipeFd = wakeFds[0]; 
  23.     mWakeWritePipeFd = wakeFds[1]; 
  24.  
  25.     result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK); 
  26.     …… 
  27.     result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK); 
  28.     …… 
  29.     result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem); 
  30.     …… 

  Android Input子系统:Input进程的创建,监听线程的启动

那么这里抛出一个问题:为什么要把管道的读端注册到epoll中?假如EventHub因为getEvents读不到事件而阻塞在epoll_wait()里,而我们没有绑定读端的话,我们要怎么唤醒EventHub?如果绑定了管道的读端,我们就可以通过向管道的写端写数据从而让EventHub因为得到管道写端的数据而被唤醒。

InputManager的创建

接下来继续说InputManager的创建,它的创建就简单多了,创建一个InputDispatcher对象用于分发事件,一个InputReader对象用于读事件并把事件交给InputDispatcher分发,,然后调用initialize()初始化,其实也就是创建了InputReaderThread和InputDispatcherThread。

 
  1. InputManager.cpp 
  2.  
  3. InputManager::InputManager(        const sp<EventHubInterface>& eventHub,        const sp<InputReaderPolicyInterface>& readerPolicy,        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) { 
  4.     mDispatcher = new InputDispatcher(dispatcherPolicy); 
  5.     mReader = new InputReader(eventHub, readerPolicy, mDispatcher); 
  6.     initialize(); 
  7. }void InputManager::initialize() { 
  8.     mReaderThread = new InputReaderThread(mReader); 
  9.     mDispatcherThread = new InputDispatcherThread(mDispatcher); 

InputDispatcher和InputReader的创建都相对简单。InputDispatcher会创建自己线程的Looper,以及设置根据传入的dispatchPolicy设置分发规则。InputReader则会将传入的InputDispatcher封装为监听对象存起来,做一些数据初始化就结束了。

至此,InputManagerService对象的初始化就完成了,根据开头说的,接下来就会调用InputManagerService的start()方法。

监听线程InputReader和InputDispatcher的启动

在start()方法中,做了以下事情:

  • 调用nativeStart方法,其实就是调用InputManager的start()方法
  • 将InputManagerService交给WatchDog监控
  • 注册触控点速度、显示触控的观察者,并注册广播监控它们
  • 主动调用updateXXX方法更新(初始化) 
 
  1. InputManagerService.javapublic void start() { 
  2.     Slog.i(TAG, "Starting input manager"); 
  3.     nativeStart(mPtr);    // Add ourself to the Watchdog monitors. 
  4.     Watchdog.getInstance().addMonitor(this); 
  5.  
  6.     registerPointerSpeedSettingObserver(); 
  7.     registerShowTouchesSettingObserver(); 
  8.     registerAccessibilityLargePointerSettingObserver(); 
  9.  
  10.     mContext.registerReceiver(new BroadcastReceiver() {        @Override 
  11.         public void onReceive(Context context, Intent intent) { 
  12.             updatePointerSpeedFromSettings(); 
  13.             updateShowTouchesFromSettings(); 
  14.             updateAccessibilityLargePointerFromSettings(); 
  15.         } 
  16.     }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler); 
  17.  
  18.     updatePointerSpeedFromSettings(); 
  19.     updateShowTouchesFromSettings(); 
  20.     updateAccessibilityLargePointerFromSettings(); 

显而易见这里最值得关注的就是InputManager的start()方法了,可惜这个方法并不值得我们如此关心,因为它做的事情很简单,就是启动InputDispatcherThread和InputReaderThread开始监听。

 
  1. status_t InputManager::start() { 
  2.     status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);    if (result) { 
  3.         ALOGE("Could not start InputDispatcher thread due to error %d.", result);        return result; 
  4.     } 
  5.  
  6.     result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);    if (result) { 
  7.         ALOGE("Could not start InputReader thread due to error %d.", result); 
  8.  
  9.         mDispatcherThread->requestExit();        return result; 
  10.     }    return OK; 

那么InputReaderThread线程是怎么和EventHub关联起来的呢?

对于InputReadThread:

  • 启动后循环执行mReader->loopOnce()
  • loopOnce()中会调用mEventHub->getEvents读取事件
  • 读到了事件就会调用processEventsLocked处理事件
  • 处理完成后调用getInputDevicesLocked获取输入设备信息
  • 调用mPolicy->notifyInputDevicesChanged函数利用InputManagerService的代理通过Handler发送MSG_DELIVER_INPUT_DEVICES_CHANGED消息,通知输入设备发生了变化
  • 最后调用mQueuedListener->flush(),将事件队列中的所有事件交给在InputReader中注册过的InputDispatcher 
 
  1. bool InputReaderThread::threadLoop() { 
  2.     mReader->loopOnce();    return true
  3. }void InputReader::loopOnce() { 
  4.     …… 
  5.  
  6.     size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE); 
  7.  
  8.     { // acquire lock 
  9.         AutoMutex _l(mLock); 
  10.         mReaderIsAliveCondition.broadcast();        if (count) { 
  11.             processEventsLocked(mEventBuffer, count); 
  12.         } 
  13.  
  14.     ……        if (oldGeneration != mGeneration) { 
  15.             inputDevicesChanged = true
  16.             getInputDevicesLocked(inputDevices); 
  17.         } 
  18.     } // release lock 
  19.  
  20.     // Send out a message that the describes the changed input devices. 
  21.     if (inputDevicesChanged) { 
  22.         mPolicy->notifyInputDevicesChanged(inputDevices); 
  23.     } 
  24.  
  25.     …… 
  26.     mQueuedListener->flush(); 

至此,Input系统有关事件输入模块的学习就结束了,在后续的文章中会继续学习Input系统的事件归类、分发流程,感兴趣的朋友可以留意关注。


本文作者: Cheeeelok

来源:51CTO

原文标题:Android Input子系统:Input进程的创建,监听线程的启动
相关文章
|
23天前
|
Java Android开发 UED
🧠Android多线程与异步编程实战!告别卡顿,让应用响应如丝般顺滑!🧵
【7月更文挑战第28天】在Android开发中,确保UI流畅性至关重要。多线程与异步编程技术可将耗时操作移至后台,避免阻塞主线程。我们通常采用`Thread`类、`Handler`与`Looper`、`AsyncTask`及`ExecutorService`等进行多线程编程。
35 2
|
12天前
|
Go 调度 开发者
[go 面试] 深入理解进程、线程和协程的概念及区别
[go 面试] 深入理解进程、线程和协程的概念及区别
|
1月前
|
安全 Python
告别低效编程!Python线程与进程并发技术详解,让你的代码飞起来!
【7月更文挑战第9天】Python并发编程提升效率:**理解并发与并行,线程借助`threading`模块处理IO密集型任务,受限于GIL;进程用`multiprocessing`实现并行,绕过GIL限制。示例展示线程和进程创建及同步。选择合适模型,注意线程安全,利用多核,优化性能,实现高效并发编程。
38 3
|
1月前
|
安全 数据安全/隐私保护 数据中心
Python并发编程大挑战:线程安全VS进程隔离,你的选择影响深远!
【7月更文挑战第9天】Python并发:线程共享内存,高效但需处理线程安全(GIL限制并发),适合IO密集型;进程独立内存,安全但通信复杂,适合CPU密集型。使用`threading.Lock`保证线程安全,`multiprocessing.Queue`实现进程间通信。选择取决于任务性质和性能需求。
52 1
|
1月前
|
Python
解锁Python并发新世界:线程与进程的并行艺术,让你的应用性能翻倍!
【7月更文挑战第9天】并发编程**是同时执行多个任务的技术,提升程序效率。Python的**threading**模块支持多线程,适合IO密集型任务,但受GIL限制。**multiprocessing**模块允许多进程并行,绕过GIL,适用于CPU密集型任务。例如,计算平方和,多线程版本使用`threading`分割工作并同步结果;多进程版本利用`multiprocessing.Pool`分块计算再合并。正确选择能优化应用性能。
22 1
|
5天前
|
算法 Java
JUC(1)线程和进程、并发和并行、线程的状态、lock锁、生产者和消费者问题
该博客文章综合介绍了Java并发编程的基础知识,包括线程与进程的区别、并发与并行的概念、线程的生命周期状态、`sleep`与`wait`方法的差异、`Lock`接口及其实现类与`synchronized`关键字的对比,以及生产者和消费者问题的解决方案和使用`Condition`对象替代`synchronized`关键字的方法。
JUC(1)线程和进程、并发和并行、线程的状态、lock锁、生产者和消费者问题
|
8天前
|
调度 Android开发 开发者
【颠覆传统!】Kotlin协程魔法:解锁Android应用极速体验,带你领略多线程优化的无限魅力!
【8月更文挑战第12天】多线程对现代Android应用至关重要,能显著提升性能与体验。本文探讨Kotlin中的高效多线程实践。首先,理解主线程(UI线程)的角色,避免阻塞它。Kotlin协程作为轻量级线程,简化异步编程。示例展示了如何使用`kotlinx.coroutines`库创建协程,执行后台任务而不影响UI。此外,通过协程与Retrofit结合,实现了网络数据的异步加载,并安全地更新UI。协程不仅提高代码可读性,还能确保程序高效运行,不阻塞主线程,是构建高性能Android应用的关键。
27 4
|
11天前
|
Android开发 开发者 Kotlin
Android 多进程情况下判断应用是否处于前台或者后台
本文介绍在多进程环境下判断Android应用前后台状态的方法。通过`ActivityManager`和服务信息`RunningAppProcessInfo`可有效检测应用状态,优化资源使用。提供Kotlin代码示例,帮助开发者轻松集成。
66 8
|
20天前
|
消息中间件 程序员 调度
如何区分进程、线程和协程?看这篇就够了!
以下是内容摘要,已简化并保持在240字符以内: 嗨,我是小米!今天聊聊进程、线程和协程: - **进程**:资源分配基本单位,独立且隔离。 - **线程**:进程内执行单元,轻量级且共享资源。 - **协程**:比线程更轻量,适合I/O密集型任务。 每种都有独特特点和适用场景,选择合适可优化性能。希望对你有所帮助!更多内容,请关注我的公众号“软件求生”。
28 1
|
26天前
|
存储 缓存 NoSQL
Redis性能优化问题之优化 Redis fork 耗时严重的问题,如何解决
Redis性能优化问题之优化 Redis fork 耗时严重的问题,如何解决

相关实验场景

更多