上接: Android应用程序键盘(Keyboard)消息处理机制分析(二) -------

3.InputManager分发键盘消息给应用程序的过程分析

在分析InputManager分发键盘消息给应用程序的过程之前,我们先假设现在没有键盘事件发生,因此,InputManager中的InputReader正在睡眠等待键盘事件的发生,而InputManager中的InputDispatcher正在等待InputReader从睡眠中醒过来并且唤醒它,而应用程序也正在消息循环中等待InputDispatcher从睡眠中醒过来并且唤醒它。这时候,用户按下键盘中的一个键,于是,一系列唤醒的事件就依次发生了,一直到应用程序中正在显示的Activity得到通知,有键盘事件发生了。我们先来看这个过程的序列图,然后再详细分析每一个步骤:


点击查看大图

Step 1. InputReader.pollOnce

Step 2. EventHub.getEvent

这两个函数分别定义在frameworks/base/libs/ui/InputReader.cpp和frameworks/base/libs/ui/EventHub.cpp文件中,前面我们在分析InputManager的启动过程的Step 17和Step 18时,已经看到过这两个函数了。InputReaderThread线程会不民地循环调用InputReader.pollOnce函数来读入键盘事件,而实际的键盘事件读入操作是由EventHub.getEvent函数来进行的。如果当前没有键盘事件发生,InputReaderThread线程就会睡眠在EventHub.getEvent函数上,而当键盘事件发生后,就会把这个事件封装成一个RawEvent对象,然后返回到pollOnce函数中,执行process函数进一步处理:

view plain
  1. voidInputReader::loopOnce(){
  2. RawEventrawEvent;
  3. mEventHub->getEvent(&rawEvent);
  4. ......
  5. process(&rawEvent);
  6. }
Step 3. InputReader.process

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

view plain
  1. voidInputReader::process(constRawEvent*rawEvent){
  2. switch(rawEvent->type){
  3. caseEventHubInterface::DEVICE_ADDED:
  4. addDevice(rawEvent->deviceId);
  5. break;
  6. caseEventHubInterface::DEVICE_REMOVED:
  7. removeDevice(rawEvent->deviceId);
  8. break;
  9. caseEventHubInterface::FINISHED_DEVICE_SCAN:
  10. handleConfigurationChanged(rawEvent->when);
  11. break;
  12. default:
  13. consumeEvent(rawEvent);
  14. break;
  15. }
  16. }

当键盘事件发生时,rawEvent->type的值为EV_KEY,这是一个宏定义,具体可以参考bionic/libc/kernel/common/linux/input.h文件:

view plain
  1. #defineEV_KEY0x01
因此,接下来会调用consumeEvent函数进一步处理。

Step 4.InputReader.consumeEvent

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

view plain
  1. voidInputReader::consumeEvent(constRawEvent*rawEvent){
  2. int32_tdeviceId=rawEvent->deviceId;
  3. {//acquiredeviceregistryreaderlock
  4. RWLock::AutoRLock_rl(mDeviceRegistryLock);
  5. ssize_tdeviceIndex=mDevices.indexOfKey(deviceId);
  6. if(deviceIndex<0){
  7. LOGW("DiscardingeventforunknowndeviceId%d.",deviceId);
  8. return;
  9. }
  10. InputDevice*device=mDevices.valueAt(deviceIndex);
  11. if(device->isIgnored()){
  12. //LOGD("DiscardingeventforignoreddeviceId%d.",deviceId);
  13. return;
  14. }
  15. device->process(rawEvent);
  16. }//releasedeviceregistryreaderlock
  17. }
首先从rawEvent中取得触发键盘事件设备对象device,然后调用它的process函数进行处理。

Step 5. InputDevice.process

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

view plain
  1. voidInputDevice::process(constRawEvent*rawEvent){
  2. size_tnumMappers=mMappers.size();
  3. for(size_ti=0;i<numMappers;i++){
  4. InputMapper*mapper=mMappers[i];
  5. mapper->process(rawEvent);
  6. }
  7. }
这里的mMapper成员变量保存了一系列输入设备事件处理象,例如负责处理键盘事件的KeyboardKeyMapper对象、负责处理轨迹球事件的TrackballInputMapper对象以及负责处理触摸屏事件的TouchInputMapper对象, 它们是在InputReader类的成员函数createDevice中创建的。这里查询每一个InputMapper对象是否要对当前发生的事件进行处理。由于发生的是键盘事件,真正会对该事件进行处理的只有KeyboardKeyMapper对象。

Step 6.KeyboardInputMapper.process

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

view plain
  1. voidKeyboardInputMapper::process(constRawEvent*rawEvent){
  2. switch(rawEvent->type){
  3. caseEV_KEY:{
  4. int32_tscanCode=rawEvent->scanCode;
  5. if(isKeyboardOrGamepadKey(scanCode)){
  6. processKey(rawEvent->when,rawEvent->value!=0,rawEvent->keyCode,scanCode,
  7. rawEvent->flags);
  8. }
  9. break;
  10. }
  11. }
  12. }
这个函数首先会检查一下键盘扫描码是否正确,如果正确的话,就会调用processKey函数进一步处理。

Step 7.KeyboardInputMapper.processKey

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

view plain
  1. voidKeyboardInputMapper::processKey(nsecs_twhen,booldown,int32_tkeyCode,
  2. int32_tscanCode,uint32_tpolicyFlags){
  3. int32_tnewMetaState;
  4. nsecs_tdownTime;
  5. boolmetaStateChanged=false;
  6. {//acquirelock
  7. AutoMutex_l(mLock);
  8. if(down){
  9. //Rotatekeycodesaccordingtoorientationifneeded.
  10. //Note:getDisplayInfoisnon-reentrantsowecancontinueholdingthelock.
  11. if(mAssociatedDisplayId>=0){
  12. int32_torientation;
  13. if(!getPolicy()->getDisplayInfo(mAssociatedDisplayId,NULL,NULL,&orientation)){
  14. return;
  15. }
  16. keyCode=rotateKeyCode(keyCode,orientation);
  17. }
  18. //Addkeydown.
  19. ssize_tkeyDownIndex=findKeyDownLocked(scanCode);
  20. if(keyDownIndex>=0){
  21. //keyrepeat,besuretousesamekeycodeasbeforeincaseofrotation
  22. keyCode=mLocked.keyDowns.itemAt(keyDownIndex).keyCode;
  23. }else{
  24. //keydown
  25. if((policyFlags&POLICY_FLAG_VIRTUAL)
  26. &&mContext->shouldDropVirtualKey(when,getDevice(),keyCode,scanCode)){
  27. return;
  28. }
  29. mLocked.keyDowns.push();
  30. KeyDown&keyDown=mLocked.keyDowns.editTop();
  31. keyDown.keyCode=keyCode;
  32. keyDown.scanCode=scanCode;
  33. }
  34. mLocked.downTime=when;
  35. }else{
  36. //Removekeydown.
  37. ssize_tkeyDownIndex=findKeyDownLocked(scanCode);
  38. if(keyDownIndex>=0){
  39. //keyup,besuretousesamekeycodeasbeforeincaseofrotation
  40. keyCode=mLocked.keyDowns.itemAt(keyDownIndex).keyCode;
  41. mLocked.keyDowns.removeAt(size_t(keyDownIndex));
  42. }else{
  43. //keywasnotactuallydown
  44. LOGI("Droppingkeyupfromdevice%sbecausethekeywasnotdown."
  45. "keyCode=%d,scanCode=%d",
  46. getDeviceName().string(),keyCode,scanCode);
  47. return;
  48. }
  49. }
  50. int32_toldMetaState=mLocked.metaState;
  51. newMetaState=updateMetaState(keyCode,down,oldMetaState);
  52. if(oldMetaState!=newMetaState){
  53. mLocked.metaState=newMetaState;
  54. metaStateChanged=true;
  55. }
  56. downTime=mLocked.downTime;
  57. }//releaselock
  58. if(metaStateChanged){
  59. getContext()->updateGlobalMetaState();
  60. }
  61. getDispatcher()->notifyKey(when,getDeviceId(),AINPUT_SOURCE_KEYBOARD,policyFlags,
  62. down?AKEY_EVENT_ACTION_DOWN:AKEY_EVENT_ACTION_UP,
  63. AKEY_EVENT_FLAG_FROM_SYSTEM,keyCode,scanCode,newMetaState,downTime);
  64. }
这个函数首先对对按键作一些处理,例如,当某一个DPAD键被按下时,根据当时屏幕方向的不同,它所表示的意义也不同,因此,这里需要根据当时屏幕的方向来调整键盘码:
view plain
  1. //Rotatekeycodesaccordingtoorientationifneeded.
  2. //Note:getDisplayInfoisnon-reentrantsowecancontinueholdingthelock.
  3. if(mAssociatedDisplayId>=0){
  4. int32_torientation;
  5. if(!getPolicy()->getDisplayInfo(mAssociatedDisplayId,NULL,NULL,&orientation)){
  6. return;
  7. }
  8. keyCode=rotateKeyCode(keyCode,orientation);
  9. }
如果这个键是一直按着不放的,不管屏幕的方向如何,必须保证后面的键盘码和前面的一样:

view plain
  1. //Addkeydown.
  2. ssize_tkeyDownIndex=findKeyDownLocked(scanCode);
  3. if(keyDownIndex>=0){
  4. //keyrepeat,besuretousesamekeycodeasbeforeincaseofrotation
  5. keyCode=mLocked.keyDowns.itemAt(keyDownIndex).keyCode;
  6. }else{
  7. //keydown
  8. if((policyFlags&POLICY_FLAG_VIRTUAL)
  9. &&mContext->shouldDropVirtualKey(when,getDevice(),keyCode,scanCode)){
  10. return;
  11. }
  12. mLocked.keyDowns.push();
  13. KeyDown&keyDown=mLocked.keyDowns.editTop();
  14. keyDown.keyCode=keyCode;
  15. keyDown.scanCode=scanCode;
  16. }
如果是第一次按下某个键,还必须把它保存在mLocked.keyDowns里面,就是为了处理上面讲的当这个键盘一直按着不放的时候屏幕方向发生改变的情况。
如果是松开键盘上的某个键,就把它从mLocked.keyDowns里面删除:

view plain
  1. //Removekeydown.
  2. ssize_tkeyDownIndex=findKeyDownLocked(scanCode);
  3. if(keyDownIndex>=0){
  4. //keyup,besuretousesamekeycodeasbeforeincaseofrotation
  5. keyCode=mLocked.keyDowns.itemAt(keyDownIndex).keyCode;
  6. mLocked.keyDowns.removeAt(size_t(keyDownIndex));
  7. }else{
  8. //keywasnotactuallydown
  9. LOGI("Droppingkeyupfromdevice%sbecausethekeywasnotdown."
  10. "keyCode=%d,scanCode=%d",
  11. getDeviceName().string(),keyCode,scanCode);
  12. return;
  13. }
当然,对键盘事件的这些处理不是本文的重点,本文的重点是分析从键盘事件到当前激活的Activity窗口接收到这个键盘消息的过程。

最后,KeyboardInputMappger函数通知InputDispatcher,有键盘事件发生了:

view plain
  1. getDispatcher()->notifyKey(when,getDeviceId(),AINPUT_SOURCE_KEYBOARD,policyFlags,
  2. down?AKEY_EVENT_ACTION_DOWN:AKEY_EVENT_ACTION_UP,
  3. AKEY_EVENT_FLAG_FROM_SYSTEM,keyCode,scanCode,newMetaState,downTime);
Step 8. InputDispatcher.notifyKey

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

view plain
  1. voidInputDispatcher::notifyKey(nsecs_teventTime,int32_tdeviceId,int32_tsource,
  2. uint32_tpolicyFlags,int32_taction,int32_tflags,
  3. int32_tkeyCode,int32_tscanCode,int32_tmetaState,nsecs_tdownTime){
  4. ......
  5. if(!validateKeyEvent(action)){
  6. return;
  7. }
  8. /*Accordingtohttp://source.android.com/porting/keymaps_keyboard_input.html
  9. *Keydefinitions:KeydefinitionsfollowthesyntaxkeySCANCODEKEYCODE[FLAGS...],
  10. *whereSCANCODEisanumber,KEYCODEisdefinedinyourspecifickeylayoutfile
  11. *(android.keylayout.xxx),andpotentialFLAGSaredefinedasfollows:
  12. *SHIFT:Whilepressed,theshiftkeymodifierisset
  13. *ALT:Whilepressed,thealtkeymodifierisset
  14. *CAPS:Whilepressed,thecapslockkeymodifierisset
  15. *SinceKeyEvent.javadoesn'tcheckifCaplockisONandwedon'thavea
  16. *modiferstateforcaplock,wewillnotsupportit.
  17. */
  18. if(policyFlags&POLICY_FLAG_ALT){
  19. metaState|=AMETA_ALT_ON|AMETA_ALT_LEFT_ON;
  20. }
  21. if(policyFlags&POLICY_FLAG_ALT_GR){
  22. metaState|=AMETA_ALT_ON|AMETA_ALT_RIGHT_ON;
  23. }
  24. if(policyFlags&POLICY_FLAG_SHIFT){
  25. metaState|=AMETA_SHIFT_ON|AMETA_SHIFT_LEFT_ON;
  26. }
  27. policyFlags|=POLICY_FLAG_TRUSTED;
  28. mPolicy->interceptKeyBeforeQueueing(eventTime,deviceId,action,/*byref*/flags,
  29. keyCode,scanCode,/*byref*/policyFlags);
  30. boolneedWake;
  31. {//acquirelock
  32. AutoMutex_l(mLock);
  33. int32_trepeatCount=0;
  34. KeyEntry*newEntry=mAllocator.obtainKeyEntry(eventTime,
  35. deviceId,source,policyFlags,action,flags,keyCode,scanCode,
  36. metaState,repeatCount,downTime);
  37. needWake=enqueueInboundEventLocked(newEntry);
  38. }//releaselock
  39. if(needWake){
  40. mLooper->wake();
  41. }
  42. }
函数首先是调用validateKeyEvent函数来验证action参数是否正确:

view plain
  1. staticboolisValidKeyAction(int32_taction){
  2. switch(action){
  3. caseAKEY_EVENT_ACTION_DOWN:
  4. caseAKEY_EVENT_ACTION_UP:
  5. returntrue;
  6. default:
  7. returnfalse;
  8. }
  9. }
  10. staticboolvalidateKeyEvent(int32_taction){
  11. if(!isValidKeyAction(action)){
  12. LOGE("Keyeventhasinvalidactioncode0x%x",action);
  13. returnfalse;
  14. }
  15. returntrue;
  16. }
正确的action参数的值只能为AKEY_EVENT_ACTION_DOWN(按下)或者AKEY_EVENT_ACTION_UP(松开)。

参数action检查通过后,还通过policyFlags参数来检查一下同时是否有ALT和SHIFT键被按下:

view plain
  1. if(policyFlags&POLICY_FLAG_ALT){
  2. metaState|=AMETA_ALT_ON|AMETA_ALT_LEFT_ON;
  3. }
  4. if(policyFlags&POLICY_FLAG_ALT_GR){
  5. metaState|=AMETA_ALT_ON|AMETA_ALT_RIGHT_ON;
  6. }
  7. if(policyFlags&POLICY_FLAG_SHIFT){
  8. metaState|=AMETA_SHIFT_ON|AMETA_SHIFT_LEFT_ON;
  9. }
最后,调用enqueueInboundEventLocked函数把这个按键事件封装成一个KeyEntry结构加入到InputDispatcher类的mInboundQueue队列中去:

view plain
  1. boolInputDispatcher::enqueueInboundEventLocked(EventEntry*entry){
  2. boolneedWake=mInboundQueue.isEmpty();
  3. mInboundQueue.enqueueAtTail(entry);
  4. switch(entry->type){
  5. caseEventEntry::TYPE_KEY:{
  6. KeyEntry*keyEntry=static_cast<KeyEntry*>(entry);
  7. if(isAppSwitchKeyEventLocked(keyEntry)){
  8. if(keyEntry->action==AKEY_EVENT_ACTION_DOWN){
  9. mAppSwitchSawKeyDown=true;
  10. }elseif(keyEntry->action==AKEY_EVENT_ACTION_UP){
  11. if(mAppSwitchSawKeyDown){
  12. <spanstyle="white-space:pre"></span>......
  13. mAppSwitchDueTime=keyEntry->eventTime+APP_SWITCH_TIMEOUT;
  14. mAppSwitchSawKeyDown=false;
  15. needWake=true;
  16. }
  17. }
  18. }
  19. break;
  20. }
  21. }
  22. returnneedWake;
  23. }
从这个函数我们可以看出,在两种情况下,它的返回值为true,一是当加入该键盘事件到mInboundQueue之前,mInboundQueue为空,这表示InputDispatccherThread线程正在睡眠等待InputReaderThread线程的唤醒,因此,它返回true表示要唤醒InputDispatccherThread线程;二是加入该键盘事件到mInboundQueue之前,mInboundQueue不为空,但是此时用户按下的是Home键,按下Home键表示要切换App,我们知道,在切换App时,新的App会把它的键盘消息接收通道注册到InputDispatcher中去,并且会等待InputReader的唤醒,因此,在这种情况下,也需要返回true,表示要唤醒InputDispatccherThread线程。如果不是这两种情况,那么就说明InputDispatccherThread线程现在正在处理前面的键盘事件,不需要唤醒它。

回到前面的notifyKey函数中,根据enqueueInboundEventLocked函数的返回值来决定是否要唤醒InputDispatccherThread线程:

view plain
  1. if(needWake){
  2. mLooper->wake();
  3. }
这里,假设needWake为true,于是,就会调用mLooper对象的wake函数来唤醒InputDispatccherThread线程了。

Step 9. Looper.wake

这个函数定义在frameworks/base/libs/utils/Looper.cpp文件中,在前面一篇文章Android应用程序消息处理机制(Looper、Handler)分析中,我们已经分析过这个函数了,这里不再详述,简单来说,它的作用就是用来唤醒睡眠在Looper对象内部的管道读端的线程,在我们的这个场景中,睡眠在Looper对象内部的管道读端的线程就是InputDispatccherThread线程了。

从上面InputManager启动过程的Step 15中,我们知道,此时InputDispatccherThread线程正在InputDispatcher类的dispatchOnceb函数中通过调用mLooper->loopOnce函数进入睡眠状态。当它被唤醒以后,它就会从InputDispatcher类的dispatchOnceb函数返回到InputDispatcherThread类的threadLoop函数,而InputDispatcherThread类的threadLoop函数是循环执行的,于是,它又会再次进入到InputDispatcher类的dispatchOnce函数来处理当前发生的键盘事件。

Step 10.InputDispatcher.dispatchOnce

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

view plain
  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. ......
  9. }//releaselock
  10. ......
  11. }
它调用dispatchOnceInnerLocked函数来进一步处理这个键盘事件。

Step 11.InputDispatcher.dispatchOnceInnerLocked

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

view plain
  1. voidInputDispatcher::dispatchOnceInnerLocked(nsecs_tkeyRepeatTimeout,
  2. nsecs_tkeyRepeatDelay,nsecs_t*nextWakeupTime){
  3. ......
  4. //Readytostartanewevent.
  5. //Ifwedon'talreadyhaveapendingevent,gograbone.
  6. if(!mPendingEvent){
  7. if(mInboundQueue.isEmpty()){
  8. ......
  9. }else{
  10. //Inboundqueuehasatleastoneentry.
  11. EventEntry*entry=mInboundQueue.headSentinel.next;
  12. ......
  13. mInboundQueue.dequeue(entry);
  14. mPendingEvent=entry;
  15. }
  16. ......
  17. }
  18. ......
  19. switch(mPendingEvent->type){
  20. ......
  21. caseEventEntry::TYPE_KEY:{
  22. KeyEntry*typedEntry=static_cast<KeyEntry*>(mPendingEvent);
  23. ......
  24. done=dispatchKeyLocked(currentTime,typedEntry,keyRepeatTimeout,
  25. &dropReason,nextWakeupTime);
  26. break;
  27. }
  28. ......
  29. }
  30. ......
  31. }

我们忽略了这个函数的次要逻辑,主要关注键盘事件的主要处理流程。首先,如果前面发生的键盘事件都已经处理完毕,那么这里的mPendingEvent就为NULL,又因为前面我们把刚刚发生的键盘事件加入了mInboundQueue队列,因此,这里mInboundQueue不为NULL,于是,这里就把mInboundQueue队列中的键盘事件取出来,放在mPendingEvent变量中:

view plain
  1. mInboundQueue.dequeue(entry);
  2. mPendingEvent=entry;
由于这里发生的是键盘事件,即mPendingEvent->type的值为EventEntry::TYPE_KEY,于是,在接下来的switch语句中就会执行dispatchKeyLocked函数来分发键盘消息。

Step 12.InputDispatcher.dispatchKeyLocked

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

view plain
  1. boolInputDispatcher::dispatchKeyLocked(
  2. nsecs_tcurrentTime,KeyEntry*entry,nsecs_tkeyRepeatTimeout,
  3. DropReason*dropReason,nsecs_t*nextWakeupTime){
  4. ......
  5. //Identifytargets.
  6. if(!mCurrentInputTargetsValid){
  7. int32_tinjectionResult=findFocusedWindowTargetsLocked(currentTime,
  8. entry,nextWakeupTime);
  9. ......
  10. }
  11. //Dispatchthekey.
  12. dispatchEventToCurrentInputTargetsLocked(currentTime,entry,false);
  13. returntrue;
  14. }
InputDispatcher类中的mCurrentInputTargetsValid成员变量表示InputDispatcher是否已经标志出谁是当前激活的Activity窗口,如果没有,就需要通过findFocusedWindowTargetsLocked函数来把它找出来。当把当前激活的Activity窗口找出来以后,接下来就调用dispatchEventToCurrentInputTargetsLocked函数把键盘事件分发给它了。

我们先来看一InputDispatcher是如何找到当前激活的Activity窗口的,然后再分析它把键盘事件分发给当前激活Activity窗口的过程。

Step 13.InputDispatcher.findFocusedWindowTargetsLocked

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

view plain
  1. int32_tInputDispatcher::findFocusedWindowTargetsLocked(nsecs_tcurrentTime,
  2. constEventEntry*entry,nsecs_t*nextWakeupTime){
  3. mCurrentInputTargets.clear();
  4. int32_tinjectionResult;
  5. //Ifthereisnocurrentlyfocusedwindowandnofocusedapplication
  6. //thendroptheevent.
  7. if(!mFocusedWindow){
  8. if(mFocusedApplication){
  9. ......
  10. injectionResult=handleTargetsNotReadyLocked(currentTime,entry,
  11. mFocusedApplication,NULL,nextWakeupTime);
  12. gotoUnresponsive;
  13. }
  14. ......
  15. injectionResult=INPUT_EVENT_INJECTION_FAILED;
  16. gotoFailed;
  17. }
  18. //Checkpermissions.
  19. if(!checkInjectionPermission(mFocusedWindow,entry->injectionState)){
  20. injectionResult=INPUT_EVENT_INJECTION_PERMISSION_DENIED;
  21. gotoFailed;
  22. }
  23. //Ifthecurrentlyfocusedwindowispausedthenkeepwaiting.
  24. if(mFocusedWindow->paused){
  25. ......
  26. injectionResult=handleTargetsNotReadyLocked(currentTime,entry,
  27. mFocusedApplication,mFocusedWindow,nextWakeupTime);
  28. gotoUnresponsive;
  29. }
  30. //Ifthecurrentlyfocusedwindowisstillworkingonpreviouseventsthenkeepwaiting.
  31. if(!isWindowFinishedWithPreviousInputLocked(mFocusedWindow)){
  32. ......
  33. injectionResult=handleTargetsNotReadyLocked(currentTime,entry,
  34. mFocusedApplication,mFocusedWindow,nextWakeupTime);
  35. gotoUnresponsive;
  36. }
  37. //Success!Outputtargets.
  38. injectionResult=INPUT_EVENT_INJECTION_SUCCEEDED;
  39. addWindowTargetLocked(mFocusedWindow,InputTarget::FLAG_FOREGROUND,BitSet32(0));
  40. ......
  41. returninjectionResult;
  42. }
回忆前面我们分析应用程序注册键盘消息接收通道的过程时,在Step 9中,当前处于激活状态的应用程序会通过调用InputDispatcher类setInputWindows函数把把当前获得焦点的Activity窗口设置到mFocusedWindow中去,因此,这里的mFocusedWindow不为NULL,于是,就通过了第一个if语句的检查。

第二个if语句检查权限问题,原来,这个键盘事件除了是由硬件触发的外,也可以由其它进程注入进来的,如果这个键盘事件是由其它进程注入进来的,那么entry->injectState就不为NULL,它里面包含了事件注册者的进程ID和用户ID,于是,这里就会调用checkInjectionPermission来检查这个事件注入者的进程ID和用户ID,看看它是否具有这个权限。这里我们不考虑这种情况,因此,这里的entry->injectState为NULL,于是,这个if语句的检查也通过了。

第三个if语句检查当前激活的Activity窗口是否是处于paused状态,如果是的话,也不用进一步处理了。一般情况下,当前激活的Activity窗口都是处于resumed状态的,于是,这个if语句的检查也通过了。

第四个if语句检查当前激活的Activity窗口是否还正在处理前一个键盘事件,如果是的话,那就要等待它处理完前一个键盘事件后再来处理新的键盘事件了。这里我们也假设当前激活的Activity窗口不是正在处理前面的键盘事件,因此,这个if语句的检查也通过了。

最后,就调用addWindowTargetLocked函数把当前激活的Activity窗口添加到InputDispatcher类的mCurrentInputTargets成员变量中去。

Step 14.InputDispatcher.addWindowTargetLocked

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

view plain
  1. voidInputDispatcher::addWindowTargetLocked(constInputWindow*window,int32_ttargetFlags,
  2. BitSet32pointerIds){
  3. mCurrentInputTargets.push();
  4. InputTarget&target=mCurrentInputTargets.editTop();
  5. target.inputChannel=window->inputChannel;
  6. target.flags=targetFlags;
  7. target.xOffset=-window->frameLeft;
  8. target.yOffset=-window->frameTop;
  9. target.pointerIds=pointerIds;
  10. }
这个函数简单,就是把传进来的参数window添加到mCurrentInputTargets中去就完事了,后面InputDispatcher就会从mCurrentInputTargets中取出恰当的Activity窗口,然后把键盘事件分发给它。

回到Step 12中的dispatchKeyLocked函数,它接下来就调用dispatchEventToCurrentInputTargetsLocked来进一步处理了。

Step 15.InputDispatcher.dispatchEventToCurrentInputTargetsLocked

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

view plain
  1. voidInputDispatcher::dispatchEventToCurrentInputTargetsLocked(nsecs_tcurrentTime,
  2. EventEntry*eventEntry,boolresumeWithAppendedMotionSample){
  3. ......
  4. for(size_ti=0;i<mCurrentInputTargets.size();i++){
  5. constInputTarget&inputTarget=mCurrentInputTargets.itemAt(i);
  6. ssize_tconnectionIndex=getConnectionIndexLocked(inputTarget.inputChannel);
  7. if(connectionIndex>=0){
  8. sp<Connection>connection=mConnectionsByReceiveFd.valueAt(connectionIndex);
  9. prepareDispatchCycleLocked(currentTime,connection,eventEntry,&inputTarget,
  10. resumeWithAppendedMotionSample);
  11. }else{
  12. ......
  13. }
  14. }
这个函数的实现也比较简单,前面我们已经把当前需要接受键盘事件的Activity窗口添加到mCurrentInputTargets中去了,因此,这里就分别把它们取出来,然后调用prepareDispatchCycleLocked函数把键盘事件分发给它们处理。

前面我们在分析应用程序注册键盘消息接收通道的过程时,在Step 18中(InputDispatcher.registerInputChannel),把Server端的InputChannel封装成了一个Connection,然后以这个InputChannel中的Receive Pipe Fd作为键值把这个Connection对象保存在mConnectionsByReceiveFd中。这里,既然我们已经通过mCurrentInputTargets得到了表示当前需要接收键盘事件的Activity窗口的InputTarget对象,而且这个InputTarget对象的inputChannel就表示当初在InputDispatcher中注册的Server端InputChannel,因此,这里就可以把这个Connection对象取出来,最后调用prepareDispatchCycleLocked函数来进一步处理。

Step 16.InputDispatcher.prepareDispatchCycleLocked

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

view plain
  1. voidInputDispatcher::prepareDispatchCycleLocked(nsecs_tcurrentTime,
  2. constsp<Connection>&connection,EventEntry*eventEntry,constInputTarget*inputTarget,
  3. boolresumeWithAppendedMotionSample){
  4. ......
  5. //Resumethedispatchcyclewithafreshlyappendedmotionsample.
  6. //Firstwecheckthatthelastdispatchentryintheoutboundqueueisforthesame
  7. //motioneventtowhichweappendedthemotionsample.Ifwefindsuchadispatch
  8. //entry,andifitiscurrentlyinprogressthenwetrytostreamthenewsample.
  9. boolwasEmpty=connection->outboundQueue.isEmpty();
  10. if(!wasEmpty&&resumeWithAppendedMotionSample){
  11. ......
  12. return;
  13. }
  14. //Thisisanewevent.
  15. //Enqueueanewdispatchentryontotheoutboundqueueforthisconnection.
  16. DispatchEntry*dispatchEntry=mAllocator.obtainDispatchEntry(eventEntry,//incrementsref
  17. inputTarget->flags,inputTarget->xOffset,inputTarget->yOffset);
  18. ......
  19. //Enqueuethedispatchentry.
  20. connection->outboundQueue.enqueueAtTail(dispatchEntry);
  21. //Iftheoutboundqueuewaspreviouslyempty,startthedispatchcyclegoing.
  22. if(wasEmpty){
  23. ......
  24. startDispatchCycleLocked(currentTime,connection);
  25. }
  26. }

在开始处理键盘事件之前,这个函数会检查一下传进来的参数connection中的outboundQueue事件队列是否为空,如果不为空,就要看看当前要处理的事件和outboundQueue队列中的最后一个事件是不是同一个motion事件,如果是的话,并且从上面传进来的resumeWithAppendedMotionSample参数为true,这时候就要以流水线的方式来处理这些motion事件了。在我们这个情景中,要处理的是键盘事件,因此在上面Step 12中传进来的resumeWithAppendedMotionSample参数为false,因此,我们略过这种情况。

接下来,就会把当前的键盘事件封装成一个DispatchEntry对象,然后添加到connection对象的outboundQueue队列中去,表示当前键盘事件是一个待处理的键盘事件。

当connection中的outboundQueue事件队列不为空,即wasEmpty为false时,说明当前这个Activity窗口正在处键盘事件了,因此,就不需要调用startDispatchCycleLocked来启动Activity窗口来处理这个事件了,因为一旦这个Activity窗口正在处键盘事件,它就会一直处理下去,直到它里的connection对象的outboundQueue为空为止。当connection中的outboundQueue事件队列为空时,就需要调用startDispatchCycleLocked来通知这个Activity窗口来执行键盘事件处理的流程了。

Step 17.InputDispatcher.startDispatchCycleLocked

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

view plain
  1. voidInputDispatcher::startDispatchCycleLocked(nsecs_tcurrentTime,
  2. constsp<Connection>&connection){
  3. ......
  4. DispatchEntry*dispatchEntry=connection->outboundQueue.headSentinel.next;
  5. //Markthedispatchentryasinprogress.
  6. dispatchEntry->inProgress=true;
  7. //Updatetheconnection'sinputstate.
  8. EventEntry*eventEntry=dispatchEntry->eventEntry;
  9. ......
  10. //Publishtheevent.
  11. status_tstatus;
  12. switch(eventEntry->type){
  13. caseEventEntry::TYPE_KEY:{
  14. KeyEntry*keyEntry=static_cast<KeyEntry*>(eventEntry);
  15. //Applytargetflags.
  16. int32_taction=keyEntry->action;
  17. int32_tflags=keyEntry->flags;
  18. //Publishthekeyevent.
  19. status=connection->inputPublisher.publishKeyEvent(keyEntry->deviceId,keyEntry->source,
  20. action,flags,keyEntry->keyCode,keyEntry->scanCode,
  21. keyEntry->metaState,keyEntry->repeatCount,keyEntry->downTime,
  22. keyEntry->eventTime);
  23. ......
  24. break;
  25. }
  26. ......
  27. }
  28. //Sendthedispatchsignal.
  29. status=connection->inputPublisher.sendDispatchSignal();
  30. ......
  31. }
这个函数主要围绕传进来的Connection对象做两件事情,一是从它的outboundQueue队列中取出当前需要处理的键盘事件,然后把这个事件记录在它的内部对象inputPublisher中,二是通过它的内部对象inputPublisher通知它所关联的Activity窗口,现在有键盘事件需要处理了。第一件事情是通过调用它的InputPublisher对象的publishKeyEvent函数来完成的,而第二件事情是通过调用它的InputPublisher对象的sendDispatchSignal来完成的。我们先来看InputPublisher的成员函数publishKeyEvent的实现,然后再回来分析它的另外一个成员函数sendDispatchSignal的实现。

Step 18. InputPublisher.publishKeyEvent

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

view plain
  1. status_tInputPublisher::publishKeyEvent(
  2. int32_tdeviceId,
  3. int32_tsource,
  4. int32_taction,
  5. int32_tflags,
  6. int32_tkeyCode,
  7. int32_tscanCode,
  8. int32_tmetaState,
  9. int32_trepeatCount,
  10. nsecs_tdownTime,
  11. nsecs_teventTime){
  12. ......
  13. status_tresult=publishInputEvent(AINPUT_EVENT_TYPE_KEY,deviceId,source);
  14. if(result<0){
  15. returnresult;
  16. }
  17. mSharedMessage->key.action=action;
  18. mSharedMessage->key.flags=flags;
  19. mSharedMessage->key.keyCode=keyCode;
  20. mSharedMessage->key.scanCode=scanCode;
  21. mSharedMessage->key.metaState=metaState;
  22. mSharedMessage->key.repeatCount=repeatCount;
  23. mSharedMessage->key.downTime=downTime;
  24. mSharedMessage->key.eventTime=eventTime;
  25. returnOK;
  26. }
这个函数主要就是把键盘事件记录在InputPublisher类的成员变量mSharedMessage中了,这个mSharedMessage成员变量指向的是一个匿名共享内存。

这个匿名共享内存是什么时候创建的呢?前面我们在分析应用程序注册键盘消息接收通道的过程时,在Step 18中(InputDispatcher.registerInputChannel),在把Server端的InputChannel封装成一个 Connection对象时,会调用它的initialize成员函数来执行一些初始化工作,就是在这个时候创建这个匿名共享内存的了:

view plain
  1. sp<Connection>connection=newConnection(inputChannel);
  2. status_tstatus=connection->initialize();
我们来看一下这个initialize函数的实现,它定义在frameworks/base/libs/ui/InputTransport.cpp文件中:

view plain
  1. status_tInputPublisher::initialize(){
  2. ......
  3. intashmemFd=mChannel->getAshmemFd();
  4. intresult=ashmem_get_size_region(ashmemFd);
  5. ......
  6. mAshmemSize=(size_t)result;
  7. mSharedMessage=static_cast<InputMessage*>(mmap(NULL,mAshmemSize,
  8. PROT_READ|PROT_WRITE,MAP_SHARED,ashmemFd,0));
  9. ......
  10. mPinned=true;
  11. mSharedMessage->consumed=false;
  12. returnreset();
  13. }
InputPublisher的成员变量mChannel就是指注册在InputDispatcher中的Server端InputChannel了。我们知道,这个InputChannel除了拥有一个反向管道的读端文件描述符和一个前向管道的写端文件描述符之后,还有一个匿名共享文件描述符,这个匿名共享文件描述符就是用来创建匿名共享内存mSharedMessage的了。

这个匿名共享内存mSharedMessage的作用是什么呢?原来,在InputChannel中,前向管道和反向管道的作用只是用来在Server端和Client端之间相互通知有事件发生了,但是具体是什么样的事件,还需要去读取这个匿名共享内存的内容才知道。前面我们在分析应用程序注册键盘消息接收通道的过程时,在Step 14中(InputChannel.openInputChannelPair)创建Server端和Client端的InputChannel对时,创建一个匿名共享内存,这个匿名共享内存有两个文件描述符同时指向它,其中一个放在Server端的InputChannel中,另外一个放在Client端的InputChannel中。这样,当InputDispatcher通过Server端的InputChannel的前向管道来通知Client端有键盘事件发生时,Client端只要通过它的InputChannel中的匿名共享内存文件描述符去读取匿名共享内存中的内容,就可以知道发生了什么事情了。有关匿名共享内存的相关知识,请参考Android系统匿名共享内存Ashmem(Anonymous Shared Memory)简要介绍和学习计划一文。

回到Step 17中,接下来就是调用InputPublisher的成员函数sendDispatchSignal来通知Activity窗口处理键盘事件了。

Step 19.InputPublishe.sendDispatchSignal
这个函数定义在frameworks/base/libs/ui/InputTransport.cpp文件中:

view plain
  1. status_tInputPublisher::sendDispatchSignal(){
  2. ......
  3. mWasDispatched=true;
  4. returnmChannel->sendSignal(INPUT_SIGNAL_DISPATCH);
  5. }
这个函数很简单,它通过调用内部成员变量mChannel的sendSignal函数来通知相应的Activity窗口来处理键盘事件。

Step 20. InputChannel.sendSignal

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

view plain
  1. status_tInputChannel::sendSignal(charsignal){
  2. ssize_tnWrite;
  3. do{
  4. nWrite=::write(mSendPipeFd,&signal,1);
  5. }while(nWrite==-1&&errno==EINTR);
  6. if(nWrite==1){
  7. ......
  8. returnOK;
  9. }
  10. return-errno;
  11. }
这里所谓的发送信号通知,其实是通过向其内部一个管道的写端写入一个字符来实现的。前面我们分析应用程序注册键盘消息接收通道的过程时,在Step 21中(NativeInputQueue.registerInputChannel),它把一个InputChannel注册到应用程序主线程中的Looper对象中,然后应用程序的主线程就通过这个Looper对象睡眠等待在这个InputChannel中的前向管道中有新的内容可读了,这里的mSendPipeFd就是对应这个前向管道的写端。现在既然向这个前向管道的写端写入新的内容了,于是,应用程序的主线程就被唤醒了。

在前面分析应用程序注册键盘消息接收通道过程的Step 21中,我们也说过,当应用程序的主线程因为这个InputChannel中的前向管道的写端唤醒时,NativeInputQueue的成员函数handleReceiveCallback就会被回调,因此,接下来,应用程序的主线程就会被唤醒,然后执行NativeInputQueue的成员函数handleReceiveCallback。

Step 21.NativeInputQueue.handleReceiveCallback

这个函数定义在frameworks/base/core/jni/android_view_InputQueue.cpp文件中:

view plain
  1. intNativeInputQueue::handleReceiveCallback(intreceiveFd,intevents,void*data){
  2. NativeInputQueue*q=static_cast<NativeInputQueue*>(data);
  3. JNIEnv*env=AndroidRuntime::getJNIEnv();
  4. sp<Connection>connection;
  5. InputEvent*inputEvent;
  6. jobjectinputHandlerObjLocal;
  7. jlongfinishedToken;
  8. {//acquirelock
  9. AutoMutex_l(q->mLock);
  10. ssize_tconnectionIndex=q->mConnectionsByReceiveFd.indexOfKey(receiveFd);
  11. ......
  12. connection=q->mConnectionsByReceiveFd.valueAt(connectionIndex);
  13. ......
  14. status_tstatus=connection->inputConsumer.receiveDispatchSignal();
  15. if(status){
  16. ......
  17. return0;//removethecallback
  18. }
  19. ......
  20. status=connection->inputConsumer.consume(&connection->inputEventFactory,&inputEvent);
  21. ......
  22. finishedToken=generateFinishedToken(receiveFd,connection->id,connection->messageSeqNum);
  23. inputHandlerObjLocal=env->NewLocalRef(connection->inputHandlerObjGlobal);
  24. }//releaselock
  25. ......
  26. int32_tinputEventType=inputEvent->getType();
  27. jobjectinputEventObj;
  28. jmethodIDdispatchMethodId;
  29. switch(inputEventType){
  30. caseAINPUT_EVENT_TYPE_KEY:
  31. ......
  32. inputEventObj=android_view_KeyEvent_fromNative(env,
  33. static_cast<KeyEvent*>(inputEvent));
  34. dispatchMethodId=gInputQueueClassInfo.dispatchKeyEvent;
  35. break;
  36. }
  37. ......
  38. }
  39. ......
  40. env->CallStaticVoidMethod(gInputQueueClassInfo.clazz,
  41. dispatchMethodId,inputHandlerObjLocal,inputEventObj,
  42. jlong(finishedToken));
  43. ......
  44. return1;
  45. }

这个函数首先是通过参数data获得当初注册InputChannel的NativeInputQueue对象,具体可以参考前面介绍的应用程序注册键盘消息接收通道过程的Step 21。接下来再通过参数receiveFd获得保存在这个NativeInputQueue对象中的mConnectionsByReceiveFd成员变量中的Connection对象。有了这个Connection对象后,就可以获得它内部的InputConsumer对象,这个InputConsumer对象是和上面的Step 18中介绍的InputPublisher对象相应的。

在InputChannel内部中,分别有一个InputPublisher对象和一个InputConsumer对象,对于Server端的InputChannel来说,它使用的是InputPublisher对象,通过它进行键盘消息的分发,而对于Client端的InputChannel来说,它使用的是InputConsumer对象,通过它进行键盘消息的读取。

获得了这个InputConsumer对象后首先是调用它的receiveDispatchSignal来确认是否是接收到了键盘消息的通知,如果是的话,再调用它的consume函数来把键盘事件读取出来,最后,调用Java层的回调对象InputQueue的DispatchKeyEvent来处理这个键盘事件。下面,我们就依次来分析这些过程。

Step 22.InputConsumer.receiveDispatchSignal

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

view plain
  1. status_tInputConsumer::receiveDispatchSignal(){
  2. ......
  3. charsignal;
  4. status_tresult=mChannel->receiveSignal(&signal);
  5. if(result){
  6. returnresult;
  7. }
  8. if(signal!=INPUT_SIGNAL_DISPATCH){
  9. ......
  10. returnUNKNOWN_ERROR;
  11. }
  12. returnOK;
  13. }
这个函数很简单,它通过它内部对象mChannel来从前向管道的读端读入一个字符,看看是否是前面的Step 20中写入的INPUT_SIGNAL_DISPATCH字符。

InputChannel类的receiveSignal函数也是定义在frameworks/base/libs/ui/InputTransport.cpp文件中:

view plain
  1. status_tInputChannel::receiveSignal(char*outSignal){
  2. ssize_tnRead;
  3. do{
  4. nRead=::read(mReceivePipeFd,outSignal,1);
  5. }while(nRead==-1&&errno==EINTR);
  6. if(nRead==1){
  7. ......
  8. returnOK;
  9. }
  10. ......
  11. return-errno;
  12. }
Step 23.InputConsumer.consume

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

view plain
  1. status_tInputConsumer::consume(InputEventFactoryInterface*factory,InputEvent**outEvent){
  2. ......
  3. *outEvent=NULL;
  4. intashmemFd=mChannel->getAshmemFd();
  5. intresult=ashmem_pin_region(ashmemFd,0,0);
  6. ......
  7. if(mSharedMessage->consumed){
  8. ......
  9. returnINVALID_OPERATION;
  10. }
  11. //Acquirebut*neverrelease*thesemaphore.Contentiononthesemaphoreisusedtosignal
  12. //tothepublisherthatthemessagehasbeenconsumed(orisintheprocessofbeing
  13. //consumed).Eventuallythepublisherwillreinitializethesemaphoreforthenextmessage.
  14. result=sem_wait(&mSharedMessage->semaphore);
  15. ......
  16. mSharedMessage->consumed=true;
  17. switch(mSharedMessage->type){
  18. caseAINPUT_EVENT_TYPE_KEY:{
  19. KeyEvent*keyEvent=factory->createKeyEvent();
  20. if(!keyEvent)returnNO_MEMORY;

更多相关文章

  1. Android(安卓)事件处理
  2. ListView与Button的共存问题解决
  3. Android利用ViewFlipper实现屏幕切换动画效果(上)
  4. android 给动画添加结束监听事件
  5. Android(安卓)GUI系统学习1:Gralloc
  6. Android之通过ContentProvider实现两个app(进程间)间通信以及函
  7. Ubuntu下Android(安卓)JNI初步学习之——搭建相关环境和测试Demo
  8. [已解决]Android(安卓)ListView EditView 获取焦点问题
  9. Android(安卓)SO逆向-流程控制语句及表达式运算

随机推荐

  1. Android使用okhttp框架实现带参数Get和Po
  2. 如何在android上打印dom树,render树,displa
  3. only the original thread that created
  4. Delphi for Android(安卓)(aka Delphi XE
  5. 如何直接使用Android(安卓)internal and
  6. android强制横屏息屏后重新打开时会先显
  7. android 详细解答json解析与生成 JSONObj
  8. android超多开发资源整理
  9. 关于android中进行http通信的几个问题
  10. android源代码编译