`
yanlijun250
  • 浏览: 750517 次
文章分类
社区版块
存档分类
最新评论

Android应用程序键盘(Keyboard)消息处理机制分析(一)

 
阅读更多

在Android系统中,键盘按键事件是由WindowManagerService服务来管理的,然后再以消息的形式来分发给应用程序处理,不过和普通消息不一样,它是由硬件中断触发的;在上一篇文章《Android应用程序消息处理机制(Looper、Handler)分析》中,我们分析了Android应用程序的消息处理机制,本文将结合这种消息处理机制来详细分析Android应用程序是如何获得键盘按键消息的。


在系统启动的时候,SystemServer会启动窗口管理服务WindowManagerService,WindowManagerService在启动的时候就会通过系统输入管理器InputManager来总负责监控键盘消息。这些键盘消息一般都是分发给当前激活的Activity窗口来处理的,因此,当前激活的Activity窗口在创建的时候,会到WindowManagerService中去注册一个接收键盘消息的通道,表明它要处理键盘消息,而当InputManager监控到有键盘消息时,就会分给给它处理。当当前激活的Activity窗口不再处于激活状态时,它也会到WindowManagerService中去反注册之前的键盘消息接收通道,这样,InputManager就不会再把键盘消息分发给它来处理。

由于本文的内容比较多,在接下面的章节中,我们将分为五个部分来详细描述Android应用程序获得键盘按键消息的过程,每一个部分都是具体描述键盘消息处理过程中的一个过程。结合上面的键盘消息处理框架,这四个过程分别是InputManager的启动过程、应用程序注册键盘消息接收通道的过程、InputManager分发键盘消息给应用程序的过程以及应用程序注销键盘消息接收通道的过程。为了更好地理解Android应用程序获得键盘按键消息的整个过程,建议读者首先阅读Android应用程序消息处理机制(Looper、Handler)分析一文,理解了Android应用程序的消息处理机制后,就能很好的把握本文的内容。

1. InputManager的启动过程分析

前面说过,Android系统的键盘事件是由InputManager来监控的,而InputManager是由窗口管理服务WindowManagerService来启动的。

从前面一篇文章Android系统进程Zygote启动过程的源代码分析中,我们知道在Android系统中,Zygote进程负责启动系统服务进程SystemServer,而系统服务进程SystemServer负责启动系统中的各种关键服务,例如我们在前面两篇文章Android应用程序安装过程源代码分析Android系统默认Home应用程序(Launcher)的启动过程源代码分析中提到的Package管理服务PackageManagerService和Activity管理服务ActivityManagerService。这里我们所讨论的窗口管理服务WindowManagerService也是由SystemServer来启动的,具体的启动过程这里就不再详述了,具体可以参考PackageManagerService和ActivityManagerService的启动过程。

了解了WindowManagerService的启动过程之后,我们就可以继续分析InputManager的启动过程了。我们先来看一下InputManager启动过程的序列图,然后根据这个序列图来一步步分析它的启动过程:


点击查看大图

Step 1. WindowManagerService.main

这个函数定义在frameworks/base/services/java/com/android/server/WindowManagerService.java文件

中:

  1. publicclassWindowManagerServiceextendsIWindowManager.Stub
  2. implementsWatchdog.Monitor{
  3. ......
  4. publicstaticWindowManagerServicemain(Contextcontext,
  5. PowerManagerServicepm,booleanhaveInputMethods){
  6. WMThreadthr=newWMThread(context,pm,haveInputMethods);
  7. thr.start();
  8. synchronized(thr){
  9. while(thr.mService==null){
  10. try{
  11. thr.wait();
  12. }catch(InterruptedExceptione){
  13. }
  14. }
  15. returnthr.mService;
  16. }
  17. }
  18. ......
  19. }
它通过一个线程WMThread实例来执行全局唯一的WindowManagerService实例的启动操作。这里调用WMThread实例thr的start成员函数时,会进入到WMThread实例thr的run函数中去。

Step 2. WMThread.run

这个函数定义在frameworks/base/services/java/com/android/server/WindowManagerService.java文件中:

  1. publicclassWindowManagerServiceextendsIWindowManager.Stub
  2. implementsWatchdog.Monitor{
  3. ......
  4. staticclassWMThreadextendsThread{
  5. ......
  6. publicvoidrun(){
  7. ......
  8. WindowManagerServices=newWindowManagerService(mContext,mPM,
  9. mHaveInputMethods);
  10. ......
  11. }
  12. }
  13. ......
  14. }
这里执行的主要操作就是创建一个WindowManagerService实例,这样会调用到WindowManagerService构造函数中去。

Step 3. WindowManagerService<init>

WindowManagerService类的构造函数定义在frameworks/base/services/java/com/android/server/WindowManagerService.java文件中:

  1. publicclassWindowManagerServiceextendsIWindowManager.Stub
  2. implementsWatchdog.Monitor{
  3. ......
  4. finalInputManagermInputManager;
  5. ......
  6. privateWindowManagerService(Contextcontext,PowerManagerServicepm,
  7. booleanhaveInputMethods){
  8. ......
  9. mInputManager=newInputManager(context,this);
  10. ......
  11. mInputManager.start();
  12. ......
  13. }
  14. ......
  15. }
这里我们只关心InputManager的创建过程,而忽略其它无关部分。首先是创建一个InputManager实例,然后再调用它的start成员函数来监控键盘事件。在创建InputManager实例的过程中,会执行一些初始化工作,因此,我们先进入到InputManager类的构造函数去看看,然后再回过头来分析它的start成员函数。

Step 4. InputManager<init>@java

Java层的InputManager类的构造函数定义在frameworks/base/services/java/com/android/server/InputManager.java文件中:

  1. publicclassInputManager{
  2. ......
  3. publicInputManager(Contextcontext,WindowManagerServicewindowManagerService){
  4. this.mContext=context;
  5. this.mWindowManagerService=windowManagerService;
  6. this.mCallbacks=newCallbacks();
  7. init();
  8. }
  9. ......
  10. }
这里只是简单地初始化InputManager类的一些成员变量,然后调用init函数进一步执行初始化操作。

Step 5. InputManager.init

这个函数定义在frameworks/base/services/java/com/android/server/InputManager.java文件中:

  1. publicclassInputManager{
  2. ......
  3. privatevoidinit(){
  4. Slog.i(TAG,"Initializinginputmanager");
  5. nativeInit(mCallbacks);
  6. }
  7. ......
  8. }
函数init通过调用本地方法nativeInit来执行C++层的相关初始化操作。

Step 6. InputManager.nativeInit

这个函数定义在frameworks/base/services/jni$ vi com_android_server_InputManager.cpp文件中:

  1. staticvoidandroid_server_InputManager_nativeInit(JNIEnv*env,jclassclazz,
  2. jobjectcallbacks){
  3. if(gNativeInputManager==NULL){
  4. gNativeInputManager=newNativeInputManager(callbacks);
  5. }else{
  6. LOGE("Inputmanageralreadyinitialized.");
  7. jniThrowRuntimeException(env,"Inputmanageralreadyinitialized.");
  8. }
  9. }
这个函数的作用是创建一个NativeInputManager实例,并保存在gNativeInputManager变量中。由于是第一次调用到这里,因此,gNativeInputManager为NULL,于是就会new一个NativeInputManager对象出来,这样就会执行NativeInputManager类的构造函数来执其它的初始化操作。

Step 7. NativeInputManager<init>

NativeInputManager类的构造函数定义在frameworks/base/services/jni$ vi com_android_server_InputManager.cpp文件中:

  1. NativeInputManager::NativeInputManager(jobjectcallbacksObj):
  2. mFilterTouchEvents(-1),mFilterJumpyTouchEvents(-1),mVirtualKeyQuietTime(-1),
  3. mMaxEventsPerSecond(-1),
  4. mDisplayWidth(-1),mDisplayHeight(-1),mDisplayOrientation(ROTATION_0){
  5. JNIEnv*env=jniEnv();
  6. mCallbacksObj=env->NewGlobalRef(callbacksObj);
  7. sp<EventHub>eventHub=newEventHub();
  8. mInputManager=newInputManager(eventHub,this,this);
  9. }
这里只要是创建了一个EventHub实例,并且把这个EventHub作为参数来创建InputManager对象。注意,这里的InputManager类是定义在C++层的,和前面在Java层的InputManager不一样,不过它们是对应关系。EventHub类是真正执行监控键盘事件操作的地方,后面我们会进一步分析到,现在我们主要关心InputManager实例的创建过程,它会InputManager类的构造函数里面执行一些初始化操作。

Step 8. InputManager<init>@C++

C++层的InputManager类的构造函数定义在frameworks/base/libs/ui/InputManager.cpp文件中:

  1. InputManager::InputManager(
  2. constsp<EventHubInterface>&eventHub,
  3. constsp<InputReaderPolicyInterface>&readerPolicy,
  4. constsp<InputDispatcherPolicyInterface>&dispatcherPolicy){
  5. mDispatcher=newInputDispatcher(dispatcherPolicy);
  6. mReader=newInputReader(eventHub,readerPolicy,mDispatcher);
  7. initialize();
  8. }
这里主要是创建了一个InputDispatcher对象和一个InputReader对象,并且分别保存在成员变量mDispatcher和mReader中。InputDispatcher类是负责把键盘消息分发给当前激活的Activity窗口的,而InputReader类则是通过EventHub类来实现读取键盘事件的,后面我们会进一步分析。创建了这两个对象后,还要调用initialize函数来执行其它的初始化操作。

Step 9. InputManager.initialize

这个函数定义在frameworks/base/libs/ui/InputManager.cpp文件中:

  1. voidInputManager::initialize(){
  2. mReaderThread=newInputReaderThread(mReader);
  3. mDispatcherThread=newInputDispatcherThread(mDispatcher);
  4. }
这个函数创建了一个InputReaderThread线程实例和一个InputDispatcherThread线程实例,并且分别保存在成员变量mReaderThread和mDispatcherThread中。这里的InputReader实列mReader就是通过这里的InputReaderThread线程实列mReaderThread来读取键盘事件的,而InputDispatcher实例mDispatcher则是通过这里的InputDispatcherThread线程实例mDisptacherThread来分发键盘消息的。

至此,InputManager的初始化工作就完成了,在回到Step 3中继续分析InputManager的进一步启动过程之前,我们先来作一个小结,看看这个初始化过程都做什么事情:

A. 在Java层中的WindowManagerService中创建了一个InputManager对象,由它来负责管理Android应用程序框架层的键盘消息处理;

B. 在C++层也相应地创建一个InputManager本地对象来负责监控键盘事件;

C. 在C++层中的InputManager对象中,分别创建了一个InputReader对象和一个InputDispatcher对象,前者负责读取系统中的键盘消息,后者负责把键盘消息分发出去;

D.InputReader对象和一个InputDispatcher对象分别是通过InputReaderThread线程实例和InputDispatcherThread线程实例来实键盘消息的读取和分发的。

有了这些对象之后,万事就俱备了,回到Step 3中,调用InputManager类的start函数来执行真正的启动操作。

Step 10. InputManager.start

这个函数定义在frameworks/base/services/java/com/android/server/InputManager.java文件中:

  1. publicclassInputManager{
  2. ......
  3. publicvoidstart(){
  4. Slog.i(TAG,"Startinginputmanager");
  5. nativeStart();
  6. }
  7. ......
  8. }
这个函数通过调用本地方法nativeStart来执行进一步的启动操作。

Step 11. InputManager.nativeStart

这个函数定义在frameworks/base/services/jni$ vi com_android_server_InputManager.cpp文件中:

  1. staticvoidandroid_server_InputManager_nativeStart(JNIEnv*env,jclassclazz){
  2. if(checkInputManagerUnitialized(env)){
  3. return;
  4. }
  5. status_tresult=gNativeInputManager->getInputManager()->start();
  6. if(result){
  7. jniThrowRuntimeException(env,"Inputmanagercouldnotbestarted.");
  8. }
  9. }
这里的gNativeInputManager对象是在前面的Step 6中创建的,通过它的getInputManager函数可以返回C++层的InputManager对象,接着调用这个InputManager对象的start函数。

Step 12. InputManager.start

这个函数定义在frameworks/base/libs/ui/InputManager.cpp文件中:

  1. status_tInputManager::start(){
  2. status_tresult=mDispatcherThread->run("InputDispatcher",PRIORITY_URGENT_DISPLAY);
  3. if(result){
  4. LOGE("CouldnotstartInputDispatcherthreadduetoerror%d.",result);
  5. returnresult;
  6. }
  7. result=mReaderThread->run("InputReader",PRIORITY_URGENT_DISPLAY);
  8. if(result){
  9. LOGE("CouldnotstartInputReaderthreadduetoerror%d.",result);
  10. mDispatcherThread->requestExit();
  11. returnresult;
  12. }
  13. returnOK;
  14. }
这个函数主要就是分别启动一个InputDispatcherThread线程和一个InputReaderThread线程来读取和分发键盘消息的了。这里的InputDispatcherThread线程对象mDispatcherThread和InputReaderThread线程对象是在前面的Step 9中创建的,调用了它们的run函数后,就会进入到它们的threadLoop函数中去,只要threadLoop函数返回true,函数threadLoop就会一直被循环调用,于是这两个线程就起到了不断地读取和分发键盘消息的作用。

我们先来分析InputDispatcherThread线程分发消息的过程,然后再回过头来分析InputReaderThread线程读取消息的过程。

Step 13.InputDispatcherThread.threadLoop

这个函数定义在frameworks/base/libs/ui/InputDispatcher.cpp文件中:

  1. boolInputDispatcherThread::threadLoop(){
  2. mDispatcher->dispatchOnce();
  3. returntrue;
  4. }
这里的成员变量mDispatcher即为在前面Step 8中创建的InputDispatcher对象,调用它的dispatchOnce成员函数执行一次键盘消息分发的操作。

Step 14. InputDispatcher.dispatchOnce

这个函数定义在frameworks/base/libs/ui/InputDispatcher.cpp文件中:

  1. voidInputDispatcher::dispatchOnce(){
  2. nsecs_tkeyRepeatTimeout=mPolicy->getKeyRepeatTimeout();
  3. nsecs_tkeyRepeatDelay=mPolicy->getKeyRepeatDelay();
  4. nsecs_tnextWakeupTime=LONG_LONG_MAX;
  5. {//acquirelock
  6. AutoMutex_l(mLock);
  7. dispatchOnceInnerLocked(keyRepeatTimeout,keyRepeatDelay,&nextWakeupTime);
  8. if(runCommandsLockedInterruptible()){
  9. nextWakeupTime=LONG_LONG_MIN;//forcenextpolltowakeupimmediately
  10. }
  11. }//releaselock
  12. //Waitforcallbackortimeoutorwake.(makesureweroundup,notdown)
  13. nsecs_tcurrentTime=now();
  14. int32_ttimeoutMillis;
  15. if(nextWakeupTime>currentTime){
  16. uint64_ttimeout=uint64_t(nextWakeupTime-currentTime);
  17. timeout=(timeout+999999LL)/1000000LL;
  18. timeoutMillis=timeout>INT_MAX?-1:int32_t(timeout);
  19. }else{
  20. timeoutMillis=0;
  21. }
  22. mLooper->pollOnce(timeoutMillis);
  23. }
这个函数很简单,把键盘消息交给dispatchOnceInnerLocked函数来处理,这个过程我们在后面再详细分析,然后调用mLooper->pollOnce函数等待下一次键盘事件的发生。这里的成员变量mLooper的类型为Looper,它定义在C++层中,具体可以参考前面Android应用程序消息处理机制(Looper、Handler)分析一文。

Step 15. Looper.pollOnce

这个函数定义在frameworks/base/libs/utils/Looper.cpp文件中,具体可以参考前面Android应用程序消息处理机制(Looper、Handler)分析一文,这里就不再详述了。总的来说,就是在Looper类中,会创建一个管道,当调用Looper类的pollOnce函数时,如果管道中没有内容可读,那么当前线程就会进入到空闲等待状态;当有键盘事件发生时,InputReader就会往这个管道中写入新的内容,这样就会唤醒前面正在等待键盘事件发生的线程。

InputDispatcher类分发消息的过程就暂时分析到这里,后面会有更进一步的分析,现在,我们回到Step 12中,接着分析InputReader类读取键盘事件的过程。在调用了InputReaderThread线程类的run就函数后,同样会进入到InputReaderThread线程类的threadLoop函数中去。

Step 16. InputReaderThread.threadLoop

这个函数定义在frameworks/base/libs/ui/InputReader.cpp文件中:

  1. boolInputReaderThread::threadLoop(){
  2. mReader->loopOnce();
  3. returntrue;
  4. }
这里的成员变量mReader即为在前面Step 8中创建的InputReader对象,调用它的loopOnce成员函数执行一次键盘事件的读取操作。

Step 17. InputReader.loopOnce

这个函数定义在frameworks/base/libs/ui/InputReader.cpp文件中:

  1. voidInputReader::loopOnce(){
  2. RawEventrawEvent;
  3. mEventHub->getEvent(&rawEvent);
  4. #ifDEBUG_RAW_EVENTS
  5. LOGD("Inputevent:device=0x%xtype=0x%xscancode=%dkeycode=%dvalue=%d",
  6. rawEvent.deviceId,rawEvent.type,rawEvent.scanCode,rawEvent.keyCode,
  7. rawEvent.value);
  8. #endif
  9. process(&rawEvent);
  10. }
这里通过成员函数mEventHub来负责键盘消息的读取工作,如果当前有键盘事件发生或者有键盘事件等待处理,通过mEventHub的getEvent函数就可以得到这个事件,然后交给process函数进行处理,这个函数主要就是唤醒前面的InputDispatcherThread线程,通知它有新的键盘事件发生了,它需要进行一次键盘消息的分发操作了,这个函数我们后面再进一步详细分析;如果没有键盘事件发生或者没有键盘事件等待处理,那么调用mEventHub的getEvent函数时就会进入等待状态。

Step 18. EventHub.getEvent

这个函数定义在frameworks/base/libs/ui/EventHub.cpp文件中:

  1. boolEventHub::getEvent(RawEvent*outEvent)
  2. {
  3. outEvent->deviceId=0;
  4. outEvent->type=0;
  5. outEvent->scanCode=0;
  6. outEvent->keyCode=0;
  7. outEvent->flags=0;
  8. outEvent->value=0;
  9. outEvent->when=0;
  10. //NotethatweonlyallowonecallertogetEvent(),sodon'tneed
  11. //todolockinghere...onlywhenadding/removingdevices.
  12. if(!mOpened){
  13. mError=openPlatformInput()?NO_ERROR:UNKNOWN_ERROR;
  14. mOpened=true;
  15. mNeedToSendFinishedDeviceScan=true;
  16. }
  17. for(;;){
  18. //Reportanydevicesthathadlastbeenadded/removed.
  19. if(mClosingDevices!=NULL){
  20. device_t*device=mClosingDevices;
  21. LOGV("Reportingdeviceclosed:id=0x%x,name=%s\n",
  22. device->id,device->path.string());
  23. mClosingDevices=device->next;
  24. if(device->id==mFirstKeyboardId){
  25. outEvent->deviceId=0;
  26. }else{
  27. outEvent->deviceId=device->id;
  28. }
  29. outEvent->type=DEVICE_REMOVED;
  30. outEvent->when=systemTime(SYSTEM_TIME_MONOTONIC);
  31. deletedevice;
  32. mNeedToSendFinishedDeviceScan=true;
  33. returntrue;
  34. }
  35. if(mOpeningDevices!=NULL){
  36. device_t*device=mOpeningDevices;
  37. LOGV("Reportingdeviceopened:id=0x%x,name=%s\n",
  38. device->id,device->path.string());
  39. mOpeningDevices=device->next;
  40. if(device->id==mFirstKeyboardId){
  41. outEvent->deviceId=0;
  42. }else{
  43. outEvent->deviceId=device->id;
  44. }
  45. outEvent->type=DEVICE_ADDED;
  46. outEvent->when=systemTime(SYSTEM_TIME_MONOTONIC);
  47. mNeedToSendFinishedDeviceScan=true;
  48. returntrue;
  49. }
  50. if(mNeedToSendFinishedDeviceScan){
  51. mNeedToSendFinishedDeviceScan=false;
  52. outEvent->type=FINISHED_DEVICE_SCAN;
  53. outEvent->when=systemTime(SYSTEM_TIME_MONOTONIC);
  54. returntrue;
  55. }
  56. //Grabthenextinputevent.
  57. for(;;){
  58. //Consumebufferedinputevents,ifany.
  59. if(mInputBufferIndex<mInputBufferCount){
  60. conststructinput_event&iev=mInputBufferData[mInputBufferIndex++];
  61. constdevice_t*device=mDevices[mInputDeviceIndex];
  62. LOGV("%sgot:t0=%d,t1=%d,type=%d,code=%d,v=%d",device->path.string(),
  63. (int)iev.time.tv_sec,(int)iev.time.tv_usec,iev.type,iev.code,iev.value);
  64. if(device->id==mFirstKeyboardId){
  65. outEvent->deviceId=0;
  66. }else{
  67. outEvent->deviceId=device->id;
  68. }
  69. outEvent->type=iev.type;
  70. outEvent->scanCode=iev.code;
  71. if(iev.type==EV_KEY){
  72. status_terr=device->layoutMap->map(iev.code,
  73. &outEvent->keyCode,&outEvent->flags);
  74. LOGV("iev.code=%dkeyCode=%dflags=0x%08xerr=%d\n",
  75. iev.code,outEvent->keyCode,outEvent->flags,err);
  76. if(err!=0){
  77. outEvent->keyCode=AKEYCODE_UNKNOWN;
  78. outEvent->flags=0;
  79. }
  80. }else{
  81. outEvent->keyCode=iev.code;
  82. }
  83. outEvent->value=iev.value;
  84. //Useaneventtimestampinthesametimebaseas
  85. //java.lang.System.nanoTime()andandroid.os.SystemClock.uptimeMillis()
  86. //asexpectedbytherestofthesystem.
  87. outEvent->when=systemTime(SYSTEM_TIME_MONOTONIC);
  88. returntrue;
  89. }
  90. //Finishreadingalleventsfromdevicesidentifiedinpreviouspoll().
  91. //ThiscodeassumesthatmInputDeviceIndexisinitially0andthatthe
  92. //reventsmemberofpollfdisinitializedto0whenthedeviceisfirstadded.
  93. //SincemFDs[0]isusedforinotify,weprocessregulareventsstartingatindex1.
  94. mInputDeviceIndex+=1;
  95. if(mInputDeviceIndex>=mFDCount){
  96. break;
  97. }
  98. conststructpollfd&pfd=mFDs[mInputDeviceIndex];
  99. if(pfd.revents&POLLIN){
  100. int32_treadSize=read(pfd.fd,mInputBufferData,
  101. sizeof(structinput_event)*INPUT_BUFFER_SIZE);
  102. if(readSize<0){
  103. if(errno!=EAGAIN&&errno!=EINTR){
  104. LOGW("couldnotgetevent(errno=%d)",errno);
  105. }
  106. }elseif((readSize%sizeof(structinput_event))!=0){
  107. LOGE("couldnotgetevent(wrongsize:%d)",readSize);
  108. }else{
  109. mInputBufferCount=readSize/sizeof(structinput_event);
  110. mInputBufferIndex=0;
  111. }
  112. }
  113. }
  114. ......
  115. mInputDeviceIndex=0;
  116. //Pollforevents.Mindthewakelockdance!
  117. //Weholdawakelockatalltimesexceptduringpoll().Thisworksduetosome
  118. //subtlechoreography.Whenadevicedriverhaspending(unread)events,itacquires
  119. //akernelwakelock.However,oncethelastpendingeventhasbeenread,thedevice
  120. //driverwillreleasethekernelwakelock.Topreventthesystemfromgoingtosleep
  121. //whenthishappens,theEventHubholdsontoitsownuserwakelockwhiletheclient
  122. //isprocessingevents.Thusthesystemcanonlysleepiftherearenoevents
  123. //pendingorcurrentlybeingprocessed.
  124. release_wake_lock(WAKE_LOCK_ID);
  125. intpollResult=poll(mFDs,mFDCount,-1);
  126. acquire_wake_lock(PARTIAL_WAKE_LOCK,WAKE_LOCK_ID);
  127. if(pollResult<=0){
  128. if(errno!=EINTR){
  129. LOGW("pollfailed(errno=%d)\n",errno);
  130. usleep(100000);
  131. }
  132. }
  133. }
  134. }
这个函数比较长,我们一步一步来分析。

首先,如果是第一次进入到这个函数中时,成员变量mOpened的值为false,于是就会调用openPlatformInput函数来打开系统输入设备,在本文中,我们主要讨论的输入设备就是键盘了。打开了这些输入设备文件后,就可以对这些输入设备进行是监控了。如果不是第一次进入到这个函数,那么就会分析当前有没有输入事件发生,如果有,就返回这个事件,否则就会进入等待状态,等待下一次输入事件的发生。在我们这个场景中,就是等待下一次键盘事件的发生了。

我们先分析openPlatformInput函数的实现,然后回过头来分析这个getEvent函数的具体的实现。

Step 19. EventHub.openPlatformInput

这个函数定义在frameworks/base/libs/ui/EventHub.cpp文件中:

  1. boolEventHub::openPlatformInput(void)
  2. {
  3. ......
  4. res=scanDir(device_path);
  5. if(res<0){
  6. LOGE("scandirfailedfor%s\n",device_path);
  7. }
  8. returntrue;
  9. }
这个函数主要是扫描device_path目录下的设备文件,然后打开它们,这里的变量device_path定义在frameworks/base/libs/ui/EventHub.cpp文件开始的地方:

  1. staticconstchar*device_path="/dev/input";
在设备目录/dev/input中,一般有三个设备文件存在,分别是event0、mice和mouse0设备文件,其中,键盘事件就包含在event0设备文件中了。

Step 20.EventHub.scanDir

这个函数定义在frameworks/base/libs/ui/EventHub.cpp文件中:

  1. intEventHub::scanDir(constchar*dirname)
  2. {
  3. chardevname[PATH_MAX];
  4. char*filename;
  5. DIR*dir;
  6. structdirent*de;
  7. dir=opendir(dirname);
  8. if(dir==NULL)
  9. return-1;
  10. strcpy(devname,dirname);
  11. filename=devname+strlen(devname);
  12. *filename++='/';
  13. while((de=readdir(dir))){
  14. if(de->d_name[0]=='.'&&
  15. (de->d_name[1]=='\0'||
  16. (de->d_name[1]=='.'&&de->d_name[2]=='\0')))
  17. continue;
  18. strcpy(filename,de->d_name);
  19. openDevice(devname);
  20. }
  21. closedir(dir);
  22. return0;
  23. }
根据上面一步的分析,这个函数主要就是调用openDevice函数来分别打开/dev/input/event0、/dev/input/mice和/dev/input/mouse0三个设备文件了。

Step 21.EventHub.openDevice
这个函数定义在frameworks/base/libs/ui/EventHub.cpp文件中:

  1. intEventHub::openDevice(constchar*deviceName){
  2. intversion;
  3. intfd;
  4. structpollfd*new_mFDs;
  5. device_t**new_devices;
  6. char**new_device_names;
  7. charname[80];
  8. charlocation[80];
  9. charidstr[80];
  10. structinput_idid;
  11. LOGV("Openingdevice:%s",deviceName);
  12. AutoMutex_l(mLock);
  13. fd=open(deviceName,O_RDWR);
  14. if(fd<0){
  15. LOGE("couldnotopen%s,%s\n",deviceName,strerror(errno));
  16. return-1;
  17. }
  18. ......
  19. intdevid=0;
  20. while(devid<mNumDevicesById){
  21. if(mDevicesById[devid].device==NULL){
  22. break;
  23. }
  24. devid++;
  25. }
  26. ......
  27. mDevicesById[devid].seq=(mDevicesById[devid].seq+(1<<SEQ_SHIFT))&SEQ_MASK;
  28. if(mDevicesById[devid].seq==0){
  29. mDevicesById[devid].seq=1<<SEQ_SHIFT;
  30. }
  31. new_mFDs=(pollfd*)realloc(mFDs,sizeof(mFDs[0])*(mFDCount+1));
  32. new_devices=(device_t**)realloc(mDevices,sizeof(mDevices[0])*(mFDCount+1));
  33. if(new_mFDs==NULL||new_devices==NULL){
  34. LOGE("outofmemory");
  35. return-1;
  36. }
  37. mFDs=new_mFDs;
  38. mDevices=new_devices;
  39. ......
  40. device_t*device=newdevice_t(devid|mDevicesById[devid].seq,deviceName,name);
  41. if(device==NULL){
  42. LOGE("outofmemory");
  43. return-1;
  44. }
  45. device->fd=fd;
  46. mFDs[mFDCount].fd=fd;
  47. mFDs[mFDCount].events=POLLIN;
  48. mFDs[mFDCount].revents=0;
  49. //Figureoutthekindsofeventsthedevicereports.
  50. uint8_tkey_bitmask[sizeof_bit_array(KEY_MAX+1)];
  51. memset(key_bitmask,0,sizeof(key_bitmask));
  52. LOGV("Gettingkeys...");
  53. if(ioctl(fd,EVIOCGBIT(EV_KEY,sizeof(key_bitmask)),key_bitmask)>=0){
  54. //Seeifthisisakeyboard.Ignoreeverythinginthebuttonrangeexceptfor
  55. //gamepadswhicharealsoconsideredkeyboards.
  56. if(containsNonZeroByte(key_bitmask,0,sizeof_bit_array(BTN_MISC))
  57. ||containsNonZeroByte(key_bitmask,sizeof_bit_array(BTN_GAMEPAD),
  58. sizeof_bit_array(BTN_DIGI))
  59. ||containsNonZeroByte(key_bitmask,sizeof_bit_array(KEY_OK),
  60. sizeof_bit_array(KEY_MAX+1))){
  61. device->classes|=INPUT_DEVICE_CLASS_KEYBOARD;
  62. device->keyBitmask=newuint8_t[sizeof(key_bitmask)];
  63. if(device->keyBitmask!=NULL){
  64. memcpy(device->keyBitmask,key_bitmask,sizeof(key_bitmask));
  65. }else{
  66. deletedevice;
  67. LOGE("outofmemoryallocatingkeybitmask");
  68. return-1;
  69. }
  70. }
  71. }
  72. ......
  73. if((device->classes&INPUT_DEVICE_CLASS_KEYBOARD)!=0){
  74. chartmpfn[sizeof(name)];
  75. charkeylayoutFilename[300];
  76. //amoredescriptivename
  77. device->name=name;
  78. //replaceallthespaceswithunderscores
  79. strcpy(tmpfn,name);
  80. for(char*p=strchr(tmpfn,'');p&&*p;p=strchr(tmpfn,''))
  81. *p='_';
  82. //findthe.klfileweneedforthisdevice
  83. constchar*root=getenv("ANDROID_ROOT");
  84. snprintf(keylayoutFilename,sizeof(keylayoutFilename),
  85. "%s/usr/keylayout/%s.kl",root,tmpfn);
  86. booldefaultKeymap=false;
  87. if(access(keylayoutFilename,R_OK)){
  88. snprintf(keylayoutFilename,sizeof(keylayoutFilename),
  89. "%s/usr/keylayout/%s",root,"qwerty.kl");
  90. defaultKeymap=true;
  91. }
  92. status_tstatus=device->layoutMap->load(keylayoutFilename);
  93. if(status){
  94. LOGE("Error%dloadingkeylayout.",status);
  95. }
  96. //telltheworldaboutthedevname(thedescriptivename)
  97. if(!mHaveFirstKeyboard&&!defaultKeymap&&strstr(name,"-keypad")){
  98. //thebuilt-inkeyboardhasawell-knowndeviceIDof0,
  99. //thisdevicebetternotgoaway.
  100. mHaveFirstKeyboard=true;
  101. mFirstKeyboardId=device->id;
  102. property_set("hw.keyboards.0.devname",name);
  103. }else{
  104. //ensuremFirstKeyboardIdissetto-something-.
  105. if(mFirstKeyboardId==0){
  106. mFirstKeyboardId=device->id;
  107. }
  108. }
  109. charpropName[100];
  110. sprintf(propName,"hw.keyboards.%u.devname",device->id);
  111. property_set(propName,name);
  112. //'Q'keysupport=cheaptestofwhetherthisisanalpha-capablekbd
  113. if(hasKeycodeLocked(device,AKEYCODE_Q)){
  114. device->classes|=INPUT_DEVICE_CLASS_ALPHAKEY;
  115. }
  116. //SeeifthisdevicehasaDPAD.
  117. if(hasKeycodeLocked(device,AKEYCODE_DPAD_UP)&&
  118. hasKeycodeLocked(device,AKEYCODE_DPAD_DOWN)&&
  119. hasKeycodeLocked(device,AKEYCODE_DPAD_LEFT)&&
  120. hasKeycodeLocked(device,AKEYCODE_DPAD_RIGHT)&&
  121. hasKeycodeLocked(device,AKEYCODE_DPAD_CENTER)){
  122. device->classes|=INPUT_DEVICE_CLASS_DPAD;
  123. }
  124. //Seeifthisdevicehasagamepad.
  125. for(size_ti=0;i<sizeof(GAMEPAD_KEYCODES)/sizeof(GAMEPAD_KEYCODES[0]);i++){
  126. if(hasKeycodeLocked(device,GAMEPAD_KEYCODES[i])){
  127. device->classes|=INPUT_DEVICE_CLASS_GAMEPAD;
  128. break;
  129. }
  130. }
  131. LOGI("Newkeyboard:device->id=0x%xdevname='%s'propName='%s'keylayout='%s'\n",
  132. device->id,name,propName,keylayoutFilename);
  133. }
  134. ......
  135. mDevicesById[devid].device=device;
  136. device->next=mOpeningDevices;
  137. mOpeningDevices=device;
  138. mDevices[mFDCount]=device;
  139. mFDCount++;
  140. return0;
  141. }
函数首先根据文件名来打开这个设备文件:

  1. fd=open(deviceName,O_RDWR);
系统中所有输入设备文件信息都保存在成员变量mDevicesById中,因此,先在mDevicesById找到一个空位置来保存当前打开的设备文件信息:

  1. mDevicesById[devid].seq=(mDevicesById[devid].seq+(1<<SEQ_SHIFT))&SEQ_MASK;
  2. if(mDevicesById[devid].seq==0){
  3. mDevicesById[devid].seq=1<<SEQ_SHIFT;
  4. }
找到了空闲位置后,就为这个输入设备文件创建相应的device_t信息:
  1. mDevicesById[devid].seq=(mDevicesById[devid].seq+(1<<SEQ_SHIFT))&SEQ_MASK;
  2. if(mDevicesById[devid].seq==0){
  3. mDevicesById[devid].seq=1<<SEQ_SHIFT;
  4. }
  5. new_mFDs=(pollfd*)realloc(mFDs,sizeof(mFDs[0])*(mFDCount+1));
  6. new_devices=(device_t**)realloc(mDevices,sizeof(mDevices[0])*(mFDCount+1));
  7. if(new_mFDs==NULL||new_devices==NULL){
  8. LOGE("outofmemory");
  9. return-1;
  10. }
  11. mFDs=new_mFDs;
  12. mDevices=new_devices;
  13. ......
  14. device_t*device=newdevice_t(devid|mDevicesById[devid].seq,deviceName,name);
  15. if(device==NULL){
  16. LOGE("outofmemory");
  17. return-1;
  18. }
  19. device->fd=fd;

同时,这个设备文件还会保存在数组mFDs中:

  1. mFDs[mFDCount].fd=fd;
  2. mFDs[mFDCount].events=POLLIN;
  3. mFDs[mFDCount].revents=0;
接下来查看这个设备是不是键盘:

  1. //Figureoutthekindsofeventsthedevicereports.
  2. uint8_tkey_bitmask[sizeof_bit_array(KEY_MAX+1)];
  3. memset(key_bitmask,0,sizeof(key_bitmask));
  4. LOGV("Gettingkeys...");
  5. if(ioctl(fd,EVIOCGBIT(EV_KEY,sizeof(key_bitmask)),key_bitmask)>=0){
  6. //Seeifthisisakeyboard.Ignoreeverythinginthebuttonrangeexceptfor
  7. //gamepadswhicharealsoconsideredkeyboards.
  8. if(containsNonZeroByte(key_bitmask,0,sizeof_bit_array(BTN_MISC))
  9. ||containsNonZeroByte(key_bitmask,sizeof_bit_array(BTN_GAMEPAD),
  10. sizeof_bit_array(BTN_DIGI))
  11. ||containsNonZeroByte(key_bitmask,sizeof_bit_array(KEY_OK),
  12. sizeof_bit_array(KEY_MAX+1))){
  13. device->classes|=INPUT_DEVICE_CLASS_KEYBOARD;
  14. device->keyBitmask=newuint8_t[sizeof(key_bitmask)];
  15. if(device->keyBitmask!=NULL){
  16. memcpy(device->keyBitmask,key_bitmask,sizeof(key_bitmask));
  17. }else{
  18. deletedevice;
  19. LOGE("outofmemoryallocatingkeybitmask");
  20. return-1;
  21. }
  22. }
  23. }
如果是的话,还要继续进一步初始化前面为这个设备文件所创建的device_t结构体,主要就是把结构体device的classes成员变量的INPUT_DEVICE_CLASS_KEYBOARD位置为1了,以表明这是一个键盘。
如果是键盘设备,初始化工作还未完成,还要继续设置键盘的布局等信息:

  1. if((device->classes&INPUT_DEVICE_CLASS_KEYBOARD)!=0){
  2. chartmpfn[sizeof(name)];
  3. charkeylayoutFilename[300];
  4. //amoredescriptivename
  5. device->name=name;
  6. //replaceallthespaceswithunderscores
  7. strcpy(tmpfn,name);
  8. for(char*p=strchr(tmpfn,'');p&&*p;p=strchr(tmpfn,''))
  9. *p='_';
  10. //findthe.klfileweneedforthisdevice
  11. constchar*root=getenv("ANDROID_ROOT");
  12. snprintf(keylayoutFilename,sizeof(keylayoutFilename),
  13. "%s/usr/keylayout/%s.kl",root,tmpfn);
  14. booldefaultKeymap=false;
  15. if(access(keylayoutFilename,R_OK)){
  16. snprintf(keylayoutFilename,sizeof(keylayoutFilename),
  17. "%s/usr/keylayout/%s",root,"qwerty.kl");
  18. defaultKeymap=true;
  19. }
  20. status_tstatus=device->layoutMap->load(keylayoutFilename);
  21. if(status){
  22. LOGE("Error%dloadingkeylayout.",status);
  23. }
  24. //telltheworldaboutthedevname(thedescriptivename)
  25. if(!mHaveFirstKeyboard&&!defaultKeymap&&strstr(name,"-keypad")){
  26. //thebuilt-inkeyboardhasawell-knowndeviceIDof0,
  27. //thisdevicebetternotgoaway.
  28. mHaveFirstKeyboard=true;
  29. mFirstKeyboardId=device->id;
  30. property_set("hw.keyboards.0.devname",name);
  31. }else{
  32. //ensuremFirstKeyboardIdissetto-something-.
  33. if(mFirstKeyboardId==0){
  34. mFirstKeyboardId=device->id;
  35. }
  36. }
  37. charpropName[100];
  38. sprintf(propName,"hw.keyboards.%u.devname",device->id);
  39. property_set(propName,name);
  40. //'Q'keysupport=cheaptestofwhetherthisisanalpha-capablekbd
  41. if(hasKeycodeLocked(device,AKEYCODE_Q)){
  42. device->classes|=INPUT_DEVICE_CLASS_ALPHAKEY;
  43. }
  44. //SeeifthisdevicehasaDPAD.
  45. if(hasKeycodeLocked(device,AKEYCODE_DPAD_UP)&&
  46. hasKeycodeLocked(device,AKEYCODE_DPAD_DOWN)&&
  47. hasKeycodeLocked(device,AKEYCODE_DPAD_LEFT)&&
  48. hasKeycodeLocked(device,AKEYCODE_DPAD_RIGHT)&&
  49. hasKeycodeLocked(device,AKEYCODE_DPAD_CENTER)){
  50. device->classes|=INPUT_DEVICE_CLASS_DPAD;
  51. }
  52. //Seeifthisdevicehasagamepad.
  53. for(size_ti=0;i<sizeof(GAMEPAD_KEYCODES)/sizeof(GAMEPAD_KEYCODES[0]);i++){
  54. if(hasKeycodeLocked(device,GAMEPAD_KEYCODES[i])){
  55. device->classes|=INPUT_DEVICE_CLASS_GAMEPAD;
  56. break;
  57. }
  58. }
  59. LOGI("Newkeyboard:device->id=0x%xdevname='%s'propName='%s'keylayout='%s'\n",
  60. device->id,name,propName,keylayoutFilename);
  61. }
到这里,系统中的输入设备文件就打开了。

回到Step 18中,我们继续分析EventHub.getEvent函数的实现。

在中间的for循环里面,首先会检查当前是否有输入设备被关闭,如果有,就返回一个设备移除的事件给调用方:

  1. //Reportanydevicesthathadlastbeenadded/removed.
  2. if(mClosingDevices!=NULL){
  3. device_t*device=mClosingDevices;
  4. LOGV("Reportingdeviceclosed:id=0x%x,name=%s\n",
  5. device->id,device->path.string());
  6. mClosingDevices=device->next;
  7. if(device->id==mFirstKeyboardId){
  8. outEvent->deviceId=0;
  9. }else{
  10. outEvent->deviceId=device->id;
  11. }
  12. outEvent->type=DEVICE_REMOVED;
  13. outEvent->when=systemTime(SYSTEM_TIME_MONOTONIC);
  14. deletedevice;
  15. mNeedToSendFinishedDeviceScan=true;
  16. returntrue;
  17. }
接着,检查当前是否有新的输入设备加入进来:

  1. if(mOpeningDevices!=NULL){
  2. device_t*device=mOpeningDevices;
  3. LOGV("Reportingdeviceopened:id=0x%x,name=%s\n",
  4. device->id,device->path.string());
  5. mOpeningDevices=device->next;
  6. if(device->id==mFirstKeyboardId){
  7. outEvent->deviceId=0;
  8. }else{
  9. outEvent->deviceId=device->id;
  10. }
  11. outEvent->type=DEVICE_ADDED;
  12. outEvent->when=systemTime(SYSTEM_TIME_MONOTONIC);
  13. mNeedToSendFinishedDeviceScan=true;
  14. returntrue;
  15. }
接着,再检查是否需要结束监控输入事件:

  1. if(mNeedToSendFinishedDeviceScan){
  2. mNeedToSendFinishedDeviceScan=false;
  3. outEvent->type=FINISHED_DEVICE_SCAN;
  4. outEvent->when=systemTime(SYSTEM_TIME_MONOTONIC);
  5. returntrue;
  6. }
最后,就是要检查当前是否有还未处理的输入设备事件发生了:

  1. //Grabthenextinputevent.
  2. for(;;){
  3. //Consumebufferedinputevents,ifany.
  4. if(mInputBufferIndex<mInputBufferCount){
  5. conststructinput_event&iev=mInputBufferData[mInputBufferIndex++];
  6. constdevice_t*device=mDevices[mInputDeviceIndex];
  7. LOGV("%sgot:t0=%d,t1=%d,type=%d,code=%d,v=%d",device->path.string(),
  8. (int)iev.time.tv_sec,(int)iev.time.tv_usec,iev.type,iev.code,iev.value);
  9. if(device->id==mFirstKeyboardId){
  10. outEvent->deviceId=0;
  11. }else{
  12. outEvent->deviceId=device->id;
  13. }
  14. outEvent->type=iev.type;
  15. outEvent->scanCode=iev.code;
  16. if(iev.type==EV_KEY){
  17. status_terr=device->layoutMap->map(iev.code,
  18. &outEvent->keyCode,&outEvent->flags);
  19. LOGV("iev.code=%dkeyCode=%dflags=0x%08xerr=%d\n",
  20. iev.code,outEvent->keyCode,outEvent->flags,err);
  21. if(err!=0){
  22. outEvent->keyCode=AKEYCODE_UNKNOWN;
  23. outEvent->flags=0;
  24. }
  25. }else{
  26. outEvent->keyCode=iev.code;
  27. }
  28. outEvent->value=iev.value;
  29. //Useaneventtimestampinthesametimebaseas
  30. //java.lang.System.nanoTime()andandroid.os.SystemClock.uptimeMillis()
  31. //asexpectedbytherestofthesystem.
  32. outEvent->when=systemTime(SYSTEM_TIME_MONOTONIC);
  33. returntrue;
  34. }
  35. //Finishreadingalleventsfromdevicesidentifiedinpreviouspoll().
  36. //ThiscodeassumesthatmInputDeviceIndexisinitially0andthatthe
  37. //reventsmemberofpollfdisinitializedto0whenthedeviceisfirstadded.
  38. //SincemFDs[0]isusedforinotify,weprocessregulareventsstartingatindex1.
  39. mInputDeviceIndex+=1;
  40. if(mInputDeviceIndex>=mFDCount){
  41. break;
  42. }
  43. conststructpollfd&pfd=mFDs[mInputDeviceIndex];
  44. if(pfd.revents&POLLIN){
  45. int32_treadSize=read(pfd.fd,mInputBufferData,
  46. sizeof(structinput_event)*INPUT_BUFFER_SIZE);
  47. if(readSize<0){
  48. if(errno!=EAGAIN&&errno!=EINTR){
  49. LOGW("couldnotgetevent(errno=%d)",errno);
  50. }
  51. }elseif((readSize%sizeof(structinput_event))!=0){
  52. LOGE("couldnotgetevent(wrongsize:%d)",readSize);
  53. }else{
  54. mInputBufferCount=readSize/sizeof(structinput_event);
  55. mInputBufferIndex=0;
  56. }
  57. }
  58. }
未处理的输入事件保存在成员变量mInputBufferData中,如果有的话,就可以直接返回了,否则的话,就要通过系统调用poll来等待输入设备上发生新的事件了,在我们这个场景中,就是等待键盘有键被按下或者松开了。:

  1. intpollResult=poll(mFDs,mFDCount,-1);

这里的mFDs包含了我们所要监控的输入设备的打开文件描述符,这是在前面的openPlatformInput函数中初始化的。

Step 22. poll

这是一个Linux系统的文件操作系统调用,它用来查询指定的文件列表是否有有可读写的,如果有,就马上返回,否则的话,就阻塞线程,并等待驱动程序唤醒,重新调用poll函数,或超时返回。在我们的这个场景中,就是要查询是否有键盘事件发生,如果有的话,就返回,否则的话,当前线程就睡眠等待键盘事件的发生了。

这样,InputManager的启动过程就分析完了,下面我们再分析应用程序注册键盘消息接收通道的过程。

-------

转自:老罗的Android应用程序键盘(Keyboard)消息处理机制分析

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics