当前位置: 首页 > 科技观察

AndroidInput子系统:Input进程的创建,监控线程的启动

时间:2023-03-14 22:17:56 科技观察

本文主要从系统源码的角度带大家一步步了解AndroidInput子系统。以我个人的理解,Android的Inputsystem其实是一个系统级的事件处理和分发框架。它需要的功能模块大致包括:事件读取、事件分类、事件分发。那么我们就从整个Input系统的输入源入手,了解事件是如何输入到Input系统中的。在看代码之前,我们先来思考一下。如果我们要设计一个事件分发框架的输入读取模块,应该考虑哪些子模块:driver,driver到kernel,kernel到framework)事件监听模块(这里很像设计一个server,为了及时响应client的请求,需要开启一个线程监听)event阅读模块事件分发模块现在我们至少可以知道整个学习的起点,即在Input系统中,负责监听的线程有哪些,在监听过程中他们做了什么。在开始之前,先给大家分享一张我根据本文内容画的图:InputManagerService初始化概述首先,有几个大家可以达成的共识:AndroidFramework层Service(Java)是由system_server创建的process(因为没有fork,所以都运行在system_server进程中)Service创建后,会交给运行在system_server进程中的ServiceManager进行管理。因此,对于InputManagerService的创建,我们可以在SystemServer的startOtherServices()方法中找到。此方法执行以下操作:创建一个InputManagerService对象并将其交给ServiceManager。将WindowManagerService的InputMonitor作为窗口响应事件后的回调注册到InputManagerService。完成上述工作后启动InputManagerService。SystemServer.javastartOtherServices(){...inputManager=newInputManagerService(上下文);...inputManager.setWindowManagerCallbacks(wm.getInputMonitor());inputManager.start();...}接下来,我们将逐节学习相应的处理。InputManagerService对象的创建在创建InputManagerService对象时,会完成以下工作:在DisplayThread线程中创建负责处理Message的Handler调用nativeInit初始化native层的InputManagerService,并在调用期间传入DisplayThread的消息队列初始化,使用mPtr保存native层的InputManagerService初始化完成后,将Service添加到LocalServices并存储);mUseDevInputEventForAudioJack=context以键值对形式通过Map.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);Slog.i(TAG,"Initializinginputmanager,mUseDevInputEventForAudioJack="+mUseDevInputEventForAudioJack);mPtr=nativeInit(this,mContext,mHandler.getLooper().getQueue.addService(InputManagerInternal.class,newLocalService());}这里可能有人会问,为什么InputManagerService要绑定DisplayThread呢?大家不妨想一想,无论InputEvent是如何获取、分类、分发的,它仍然需要被处理,也就意味着最终的处理结果会体现在UI上,所以InputManagerService自然而然地选择了一个更接近于线程的线程用户界面。但是问题又来了,应用运行在自己的主线程中,InputManagerService应该一个一个绑定,还是一个一个轮询?这些方法效率都太低了,那么换个方式,能不能结合某种管理或者绑定线程的方式,非常接近所有应用程序的UI呢?答案是什么?这里就不多说了,大家可以结合自己的知识去想一想。初始化native层的InputManagerService。在nativeInit函数中,将Java层的MessageQueue转换为native层的MessageQueue,然后取出Looper进行NativeInputManager的初始化。可以看出这里的重头戏是NativeInputManager的创建。这个过程做了以下几件事:将Java层的Context和InputManagerService转换为native层的Context和InputManagerService,存放在mContextObj和mServiceObj中,初始化变量,创建EventHub,创建InputManager,jobjectserviceObj,constsp&looper):mLooper(looper),mInteractive(true){JNIEnv*env=jniEnv();mContextObj=env->NewGlobalRef(contextObj);mServiceObj=env->NewGlobalRef(serviceObj);{AutoMutex_l(mLock);mLocked.systemUiVisibility=ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE;mLocked.pointerSpeed=0;mLocked.pointerGesturesEnabled=true;mLocked.showTouches=false;}mInteractive=true;speventHub=newEventHub();mInputManager,thisInput(,this);}EventHub看到这里,很多人会想一想,EventHub是什么?从英文的解释来看,就是eventhub的意思。我们在文章开头也提到了Inputsystem的事件来自driver/kernel,所以我们可以猜测EventHub是处理来自driver/kernel的meta-events的hub。接下来我们在源码中验证一下我们的思路。在创建EventHub的过程中,做了以下几件事:创建mEpollFd,监听是否有数据(是否有事件)可读创建mINotifyFd,注册到DEVICE_PATH(这里的路径是/dev/input)节点,并手交给内核使用用于监听设备节点的数据增删改事件。然后,只要有数据新增或删除的事件,epoll_wait()就会返回,这样EventHub就可以收到系统的通知,获取事件的详细信息。调用epoll_ctl函数将mEpollFd和mINotifyFd注册到epoll中。定义intwakeFd[2]为事件传输管道的Read和write两端,将读端注册到epoll中让mEpollFd监听EventHub.cppEventHub::EventHub(void):mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD),mNextDeviceId(1),mControllerNumbers()、mOpeningDevices(0)、mClosingDevices(0)、mNeedToSendFinishedDeviceScan(false)、mNeedToReopenDevices(false)、mNeedToScanDevices(true)、mPendingEventCount(0)、mPendingEventIndex(0)、mPendingINotify(false){acquire_wake_lock(PARTIAL_WAKE_LOCK_ID)WAKE_LOCK;mEpollFd=epoll_create(EPOLL_SIZE_HINT);LOG_ALWAYS_FATAL_IF(mEpollFd<0,"Couldnotcreateepollinstance.errno=%d",errno);mINotifyFd=inotify_init();intresult=inotify_add_watch(mINotifyFd,DEVICE_PATH,IN_DELETE|IN_CREATE);...结果=epoll_ctl(mEpollFd,EPOLL_CTL_ADD,mINotifyFd,&;eventItem,&;...intwakeFds[2];result=pipe(wakeFds);...mWakeReadPipeFd=wakeFds[0];mWakeWritePipeFd=wakeFds[1];result=fcntl(mWakeReadPipeFd,F_SETFL,O_NONBLOCK);...result=fcntl(mWakeWritePipeFd,F_SETFL,O_NONBLOCK);...result=epoll_ctl(mEpollFd,EPOLL_CTL_ADD,mWakeReadPipeFd,&eventItem);...}那么这里有个问题:为什么要在epoll中注册管道的读端?如果EventHub因为getEvents无法读取事件而阻塞在epoll_wait()中,而我们还没有绑定读取端,那么如何唤醒EventHub呢?如果绑定了管道的读端,我们可以向管道的写端写入数据,这样EventHub就会因为管道写端的数据而被唤醒。InputManager的创建接下来我们继续说InputManager的创建。它的创建要简单得多。创建一个用于分发Event的InputDispatcher对象,一个InputReader对象用于读取事件并将事件分发给InputDispatcher,然后调用initialize()进行初始化,实际上就是创建了InputReaderThread和InputDispatcherThread。InputManager.cppInputManager::InputManager(constsp&eventHub,constsp&readerPolicy,constsp&dispatcherPolicy){mDispatcher=newInputDispatcher(dispatcherPolicy);mReader=newInputReader(eventHub,readerPolicy,mDispatcher);invoidInputManager();::initialize(){mReaderThread=newInputReaderThread(mReader);mDispatcherThread=newInputDispatcherThread(mDispatcher);}InputDispatcher和InputReader的创建比较简单。InputDispatcher会创建自己的线程Looper,根据传入的dispatchPolicy设置分发规则。InputReader会将传入的InputDispatcher封装为监听对象并保存,做一些数据初始化后结束。至此,InputManagerService对象的初始化就完成了。按照开头说的,接下来会调用InputManagerService的start()方法。在监听线程InputReader和InputDispatcher的start()方法中,做了如下事情:指向并显示触摸观察者,并注册广播监听他们主动调用updateXXX方法更新(初始化)看门狗.getInstance().addMonitor(this);registerPointerSpeedSettingObserver();registerShowTouchesSettingObserver();registerAccessibilityLargePointerSettingObserver();mContext.registerReceiver(newBroadcastReceiver(){@OverridepublicvoidonReceive(Contextcontext,Intentintent){updatePointerSpeedFromSettings();updateShowTouchesLargePointerSettingsFromSettings();updateAccess();}},newIntentFilter(Intent.ACTION_USER_SWITCHED),null,mHandler);updatePointerSpeedFromSettings();updateShowTouchesFromSettings();updateAccessibilityLargePointerFromSettings();}显然这里最值得关注的是InputManager的start()方法,可惜这个方法并不值得我们关注,因为它做的很简单,是凯激活InputDispatcherThread和InputReaderThread开始监听status_tInputManager::start(){status_tresult=mDispatcherThread->run("InputDispatcher",PRIORITY_URGENT_DISPLAY);if(result){ALOGE("CouldnotstartInputDispatcherthreadduetoerror%d.",result);returnresult;}result=mReaderThread->run("InputReader",PRIORITY_URGENT_DISPLAY);if(result){ALOGE("CouldnotstartInputReaderthreadduetoerror%d.",result);mDispatcherThread->requestExit();returnresult;}returnOK;}那么InputReaderThread线程是如何关联EventHub的呢?对于InputReadThread:启动后会循环执行mReader->loopOnce()。在loopOnce()中,会调用mEventHub->getEvents读取事件。当事件被读取时,会调用processEventsLocked来处理事件。事件处理完成后,会调用getInputDevicesLocked获取输入设备信息。调用mPolicy->notifyInputDevicesChanged函数使用InputManagerService代理通过Handler发送MSG_DELIVER_INPUT_DEVICES_CHANGED消息通知输入设备发生变化***调用mQueuedListener->flush()将事件队列中的所有事件交给InputDispatcherboolInputReaderThread::threadLoop(){mReader->loopOnce注册到InputReader();returntrue;}voidInputReader::loopOnce(){...size_tcount=mEventHub->getEvents(timeoutMillis,mEventBuffer,EVENT_BUFFER_SIZE);{//acquirelockAutoMutex_l(mLock);mReaderIsAliveCondition.broadcast();if(count){processEventsLocked(mEventBuffer,count);}...if(oldGeneration!=mGeneration){inputDevicesChanged=true;getInputDevicesLocked(inputDevices);}}//releaselock//Sendoutamessagethatthedescribesthechangedinputdevices.if(inputDevicesChanged){mPolicy->notifyInputDevicesChanged(InputDevices);}……mQueued(Listen)事件的学习系统输入模块结束。在后续的文章中,我们将继续学习Input系统的事件分类和分发过程。感兴趣的朋友可以关注一下。