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

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

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

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

Android应用程序键盘(Keyboard)消息处理机制分析(三)_第1张图片

点击查看大图

        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. void InputReader::loopOnce() {  
  2.     RawEvent rawEvent;  
  3.     mEventHub->getEvent(& rawEvent);  
  4.   
  5.     ......  
  6.   
  7.     process(& rawEvent);  
  8. }  
        Step 3. InputReader.process

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

view plain
  1. void InputReader::process(const RawEvent* rawEvent) {  
  2.     switch (rawEvent->type) {  
  3.     case EventHubInterface::DEVICE_ADDED:  
  4.         addDevice(rawEvent->deviceId);  
  5.         break;  
  6.   
  7.     case EventHubInterface::DEVICE_REMOVED:  
  8.         removeDevice(rawEvent->deviceId);  
  9.         break;  
  10.   
  11.     case EventHubInterface::FINISHED_DEVICE_SCAN:  
  12.         handleConfigurationChanged(rawEvent->when);  
  13.         break;  
  14.   
  15.     default:  
  16.         consumeEvent(rawEvent);  
  17.         break;  
  18.     }  
  19. }  

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

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

        Step 4. InputReader.consumeEvent

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

view plain
  1. void InputReader::consumeEvent(const RawEvent* rawEvent) {  
  2.     int32_t deviceId = rawEvent->deviceId;  
  3.   
  4.     { // acquire device registry reader lock  
  5.         RWLock::AutoRLock _rl(mDeviceRegistryLock);  
  6.   
  7.         ssize_t deviceIndex = mDevices.indexOfKey(deviceId);  
  8.         if (deviceIndex < 0) {  
  9.             LOGW("Discarding event for unknown deviceId %d.", deviceId);  
  10.             return;  
  11.         }  
  12.   
  13.         InputDevice* device = mDevices.valueAt(deviceIndex);  
  14.         if (device->isIgnored()) {  
  15.             //LOGD("Discarding event for ignored deviceId %d.", deviceId);  
  16.             return;  
  17.         }  
  18.   
  19.         device->process(rawEvent);  
  20.     } // release device registry reader lock  
  21. }  
         首先从rawEvent中取得触发键盘事件设备对象device,然后调用它的process函数进行处理。

         Step 5. InputDevice.process

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

view plain
  1. void InputDevice::process(const RawEvent* rawEvent) {  
  2.     size_t numMappers = mMappers.size();  
  3.     for (size_t i = 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. void KeyboardInputMapper::process(const RawEvent* rawEvent) {  
  2.     switch (rawEvent->type) {  
  3.     case EV_KEY: {  
  4.         int32_t scanCode = 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. void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode,  
  2.         int32_t scanCode, uint32_t policyFlags) {  
  3.     int32_t newMetaState;  
  4.     nsecs_t downTime;  
  5.     bool metaStateChanged = false;  
  6.   
  7.     { // acquire lock  
  8.      AutoMutex _l(mLock);  
  9.   
  10.      if (down) {  
  11.          // Rotate key codes according to orientation if needed.  
  12.          // Note: getDisplayInfo is non-reentrant so we can continue holding the lock.  
  13.          if (mAssociatedDisplayId >= 0) {  
  14.              int32_t orientation;  
  15.              if (! getPolicy()->getDisplayInfo(mAssociatedDisplayId, NULL, NULL, & orientation)) {  
  16.                  return;  
  17.              }  
  18.   
  19.              keyCode = rotateKeyCode(keyCode, orientation);  
  20.          }  
  21.   
  22.          // Add key down.  
  23.          ssize_t keyDownIndex = findKeyDownLocked(scanCode);  
  24.          if (keyDownIndex >= 0) {  
  25.              // key repeat, be sure to use same keycode as before in case of rotation  
  26.              keyCode = mLocked.keyDowns.itemAt(keyDownIndex).keyCode;  
  27.          } else {  
  28.              // key down  
  29.              if ((policyFlags & POLICY_FLAG_VIRTUAL)  
  30.                  && mContext->shouldDropVirtualKey(when, getDevice(), keyCode, scanCode)) {  
  31.                      return;  
  32.              }  
  33.              mLocked.keyDowns.push();  
  34.              KeyDown& keyDown = mLocked.keyDowns.editTop();  
  35.              keyDown.keyCode = keyCode;  
  36.              keyDown.scanCode = scanCode;  
  37.          }  
  38.   
  39.          mLocked.downTime = when;  
  40.      } else {  
  41.          // Remove key down.  
  42.          ssize_t keyDownIndex = findKeyDownLocked(scanCode);  
  43.          if (keyDownIndex >= 0) {  
  44.              // key up, be sure to use same keycode as before in case of rotation  
  45.              keyCode = mLocked.keyDowns.itemAt(keyDownIndex).keyCode;  
  46.              mLocked.keyDowns.removeAt(size_t(keyDownIndex));  
  47.          } else {  
  48.              // key was not actually down  
  49.              LOGI("Dropping key up from device %s because the key was not down.  "  
  50.                  "keyCode=%d, scanCode=%d",  
  51.                  getDeviceName().string(), keyCode, scanCode);  
  52.              return;  
  53.          }  
  54.      }  
  55.   
  56.      int32_t oldMetaState = mLocked.metaState;  
  57.      newMetaState = updateMetaState(keyCode, down, oldMetaState);  
  58.      if (oldMetaState != newMetaState) {  
  59.          mLocked.metaState = newMetaState;  
  60.          metaStateChanged = true;  
  61.      }  
  62.   
  63.      downTime = mLocked.downTime;  
  64.     } // release lock  
  65.   
  66.   
  67.     if (metaStateChanged) {  
  68.         getContext()->updateGlobalMetaState();  
  69.     }  
  70.   
  71.     getDispatcher()->notifyKey(when, getDeviceId(), AINPUT_SOURCE_KEYBOARD, policyFlags,  
  72.         down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,  
  73.         AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, newMetaState, downTime);  
  74. }  
        这个函数首先对对按键作一些处理,例如,当某一个DPAD键被按下时,根据当时屏幕方向的不同,它所表示的意义也不同,因此,这里需要根据当时屏幕的方向来调整键盘码:
view plain
  1. // Rotate key codes according to orientation if needed.  
  2. // Note: getDisplayInfo is non-reentrant so we can continue holding the lock.  
  3. if (mAssociatedDisplayId >= 0) {  
  4.     int32_t orientation;  
  5.     if (! getPolicy()->getDisplayInfo(mAssociatedDisplayId, NULL, NULL, & orientation)) {  
  6.         return;  
  7.     }  
  8.   
  9.     keyCode = rotateKeyCode(keyCode, orientation);  
  10. }  
        如果这个键是一直按着不放的,不管屏幕的方向如何,必须保证后面的键盘码和前面的一样:

view plain
  1. // Add key down.  
  2. ssize_t keyDownIndex = findKeyDownLocked(scanCode);  
  3. if (keyDownIndex >= 0) {  
  4.     // key repeat, be sure to use same keycode as before in case of rotation  
  5.     keyCode = mLocked.keyDowns.itemAt(keyDownIndex).keyCode;  
  6. else {  
  7.     // key down  
  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. // Remove key down.  
  2. ssize_t keyDownIndex = findKeyDownLocked(scanCode);  
  3. if (keyDownIndex >= 0) {  
  4.     // key up, be sure to use same keycode as before in case of rotation  
  5.     keyCode = mLocked.keyDowns.itemAt(keyDownIndex).keyCode;  
  6.     mLocked.keyDowns.removeAt(size_t(keyDownIndex));  
  7. else {  
  8.     // key was not actually down  
  9.     LOGI("Dropping key up from device %s because the key was not down.  "  
  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. void InputDispatcher::notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t source,  
  2.     uint32_t policyFlags, int32_t action, int32_t flags,  
  3.     int32_t keyCode, int32_t scanCode, int32_t metaState, nsecs_t downTime) {  
  4.     ......  
  5.   
  6.     if (! validateKeyEvent(action)) {  
  7.         return;  
  8.     }  
  9.   
  10.     /* According to http://source.android.com/porting/keymaps_keyboard_input.html 
  11.     * Key definitions: Key definitions follow the syntax key SCANCODE KEYCODE [FLAGS...], 
  12.     * where SCANCODE is a number, KEYCODE is defined in your specific keylayout file 
  13.     * (android.keylayout.xxx), and potential FLAGS are defined as follows: 
  14.     *     SHIFT: While pressed, the shift key modifier is set 
  15.     *     ALT: While pressed, the alt key modifier is set 
  16.     *     CAPS: While pressed, the caps lock key modifier is set 
  17.     *     Since KeyEvent.java doesn't check if Cap lock is ON and we don't have a 
  18.     *     modifer state for cap lock, we will not support it. 
  19.     */  
  20.     if (policyFlags & POLICY_FLAG_ALT) {  
  21.         metaState |= AMETA_ALT_ON | AMETA_ALT_LEFT_ON;  
  22.     }  
  23.     if (policyFlags & POLICY_FLAG_ALT_GR) {  
  24.         metaState |= AMETA_ALT_ON | AMETA_ALT_RIGHT_ON;  
  25.     }  
  26.     if (policyFlags & POLICY_FLAG_SHIFT) {  
  27.         metaState |= AMETA_SHIFT_ON | AMETA_SHIFT_LEFT_ON;  
  28.     }  
  29.   
  30.     policyFlags |= POLICY_FLAG_TRUSTED;  
  31.     mPolicy->interceptKeyBeforeQueueing(eventTime, deviceId, action, /*byref*/ flags,  
  32.         keyCode, scanCode, /*byref*/ policyFlags);  
  33.   
  34.     bool needWake;  
  35.     { // acquire lock  
  36.         AutoMutex _l(mLock);  
  37.   
  38.         int32_t repeatCount = 0;  
  39.         KeyEntry* newEntry = mAllocator.obtainKeyEntry(eventTime,  
  40.             deviceId, source, policyFlags, action, flags, keyCode, scanCode,  
  41.             metaState, repeatCount, downTime);  
  42.   
  43.         needWake = enqueueInboundEventLocked(newEntry);  
  44.     } // release lock  
  45.   
  46.     if (needWake) {  
  47.         mLooper->wake();  
  48.     }  
  49. }  
        函数首先是调用validateKeyEvent函数来验证action参数是否正确:

view plain
  1. static bool isValidKeyAction(int32_t action) {  
  2.     switch (action) {  
  3.     case AKEY_EVENT_ACTION_DOWN:  
  4.     case AKEY_EVENT_ACTION_UP:  
  5.         return true;  
  6.     default:  
  7.         return false;  
  8.     }  
  9. }  
  10.   
  11. static bool validateKeyEvent(int32_t action) {  
  12.     if (! isValidKeyAction(action)) {  
  13.         LOGE("Key event has invalid action code 0x%x", action);  
  14.         return false;  
  15.     }  
  16.     return true;  
  17. }  
        正确的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. bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {  
  2.     bool needWake = mInboundQueue.isEmpty();  
  3.     mInboundQueue.enqueueAtTail(entry);  
  4.   
  5.     switch (entry->type) {  
  6.     case EventEntry::TYPE_KEY: {  
  7.         KeyEntry* keyEntry = static_cast<KeyEntry*>(entry);  
  8.         if (isAppSwitchKeyEventLocked(keyEntry)) {  
  9.             if (keyEntry->action == AKEY_EVENT_ACTION_DOWN) {  
  10.                 mAppSwitchSawKeyDown = true;  
  11.             } else if (keyEntry->action == AKEY_EVENT_ACTION_UP) {  
  12.                 if (mAppSwitchSawKeyDown) {  
  13. <span style="white-space:pre">        </span>    ......  
  14.                     mAppSwitchDueTime = keyEntry->eventTime + APP_SWITCH_TIMEOUT;  
  15.                     mAppSwitchSawKeyDown = false;  
  16.                     needWake = true;  
  17.                 }  
  18.             }  
  19.         }  
  20.         break;  
  21.     }  
  22.     }  
  23.   
  24.     return needWake;  
  25. }  
        从这个函数我们可以看出,在两种情况下,它的返回值为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. void InputDispatcher::dispatchOnce() {  
  2.     nsecs_t keyRepeatTimeout = mPolicy->getKeyRepeatTimeout();  
  3.     nsecs_t keyRepeatDelay = mPolicy->getKeyRepeatDelay();  
  4.   
  5.     nsecs_t nextWakeupTime = LONG_LONG_MAX;  
  6.     { // acquire lock  
  7.         AutoMutex _l(mLock);  
  8.         dispatchOnceInnerLocked(keyRepeatTimeout, keyRepeatDelay, & nextWakeupTime);  
  9.   
  10.         ......  
  11.     } // release lock  
  12.   
  13.     ......  
  14. }  
        它调用dispatchOnceInnerLocked函数来进一步处理这个键盘事件。

        Step 11. InputDispatcher.dispatchOnceInnerLocked

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

view plain
  1. void InputDispatcher::dispatchOnceInnerLocked(nsecs_t keyRepeatTimeout,  
  2.     nsecs_t keyRepeatDelay, nsecs_t* nextWakeupTime) {  
  3.     ......  
  4.   
  5.     // Ready to start a new event.  
  6.     // If we don't already have a pending event, go grab one.  
  7.     if (! mPendingEvent) {  
  8.         if (mInboundQueue.isEmpty()) {  
  9.             ......  
  10.         } else {  
  11.             // Inbound queue has at least one entry.  
  12.             EventEntry* entry = mInboundQueue.headSentinel.next;  
  13.   
  14.             ......  
  15.   
  16.             mInboundQueue.dequeue(entry);  
  17.             mPendingEvent = entry;  
  18.         }  
  19.   
  20.         ......  
  21.     }  
  22.   
  23.     ......  
  24.   
  25.     switch (mPendingEvent->type) {  
  26.     ......  
  27.   
  28.     case EventEntry::TYPE_KEY: {  
  29.         KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);  
  30.         ......  
  31.         done = dispatchKeyLocked(currentTime, typedEntry, keyRepeatTimeout,  
  32.             &dropReason, nextWakeupTime);  
  33.         break;  
  34.                                }  
  35.   
  36.     ......  
  37.     }  
  38.   
  39.     ......  
  40. }  

        我们忽略了这个函数的次要逻辑,主要关注键盘事件的主要处理流程。首先,如果前面发生的键盘事件都已经处理完毕,那么这里的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. bool InputDispatcher::dispatchKeyLocked(  
  2.         nsecs_t currentTime, KeyEntry* entry, nsecs_t keyRepeatTimeout,  
  3.         DropReason* dropReason, nsecs_t* nextWakeupTime) {  
  4.     ......  
  5.   
  6.     // Identify targets.  
  7.     if (! mCurrentInputTargetsValid) {  
  8.         int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,  
  9.             entry, nextWakeupTime);  
  10.   
  11.         ......  
  12.     }  
  13.   
  14.     // Dispatch the key.  
  15.     dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false);  
  16.     return true;  
  17. }  
         InputDispatcher类中的mCurrentInputTargetsValid成员变量表示InputDispatcher是否已经标志出谁是当前激活的Activity窗口,如果没有,就需要通过findFocusedWindowTargetsLocked函数来把它找出来。当把当前激活的Activity窗口找出来以后,接下来就调用dispatchEventToCurrentInputTargetsLocked函数把键盘事件分发给它了。

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

        Step 13. InputDispatcher.findFocusedWindowTargetsLocked

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

view plain
  1. int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime,  
  2.         const EventEntry* entry, nsecs_t* nextWakeupTime) {  
  3.     mCurrentInputTargets.clear();  
  4.   
  5.     int32_t injectionResult;  
  6.   
  7.     // If there is no currently focused window and no focused application  
  8.     // then drop the event.  
  9.     if (! mFocusedWindow) {  
  10.         if (mFocusedApplication) {  
  11.             ......  
  12.             injectionResult = handleTargetsNotReadyLocked(currentTime, entry,  
  13.                 mFocusedApplication, NULL, nextWakeupTime);  
  14.             goto Unresponsive;  
  15.         }  
  16.   
  17.         ......  
  18.         injectionResult = INPUT_EVENT_INJECTION_FAILED;  
  19.         goto Failed;  
  20.     }  
  21.   
  22.     // Check permissions.  
  23.     if (! checkInjectionPermission(mFocusedWindow, entry->injectionState)) {  
  24.         injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;  
  25.         goto Failed;  
  26.     }  
  27.   
  28.     // If the currently focused window is paused then keep waiting.  
  29.     if (mFocusedWindow->paused) {  
  30.         ......  
  31.         injectionResult = handleTargetsNotReadyLocked(currentTime, entry,  
  32.             mFocusedApplication, mFocusedWindow, nextWakeupTime);  
  33.         goto Unresponsive;  
  34.     }  
  35.   
  36.     // If the currently focused window is still working on previous events then keep waiting.  
  37.     if (! isWindowFinishedWithPreviousInputLocked(mFocusedWindow)) {  
  38.         ......  
  39.         injectionResult = handleTargetsNotReadyLocked(currentTime, entry,  
  40.             mFocusedApplication, mFocusedWindow, nextWakeupTime);  
  41.         goto Unresponsive;  
  42.     }  
  43.   
  44.     // Success!  Output targets.  
  45.     injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;  
  46.     addWindowTargetLocked(mFocusedWindow, InputTarget::FLAG_FOREGROUND, BitSet32(0));  
  47.   
  48.     ......  
  49.   
  50.     return injectionResult;  
  51. }  
        回忆前面我们分析应用程序注册键盘消息接收通道的过程时,在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. void InputDispatcher::addWindowTargetLocked(const InputWindow* window, int32_t targetFlags,  
  2.         BitSet32 pointerIds) {  
  3.     mCurrentInputTargets.push();  
  4.   
  5.     InputTarget& target = mCurrentInputTargets.editTop();  
  6.     target.inputChannel = window->inputChannel;  
  7.     target.flags = targetFlags;  
  8.     target.xOffset = - window->frameLeft;  
  9.     target.yOffset = - window->frameTop;  
  10.     target.pointerIds = pointerIds;  
  11. }  
        这个函数简单,就是把传进来的参数window添加到mCurrentInputTargets中去就完事了,后面InputDispatcher就会从mCurrentInputTargets中取出恰当的Activity窗口,然后把键盘事件分发给它。

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

        Step 15. InputDispatcher.dispatchEventToCurrentInputTargetsLocked

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

view plain
  1. void InputDispatcher::dispatchEventToCurrentInputTargetsLocked(nsecs_t currentTime,  
  2.         EventEntry* eventEntry, bool resumeWithAppendedMotionSample) {  
  3.    ......  
  4.   
  5.    for (size_t i = 0; i < mCurrentInputTargets.size(); i++) {  
  6.        const InputTarget& inputTarget = mCurrentInputTargets.itemAt(i);  
  7.   
  8.        ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);  
  9.        if (connectionIndex >= 0) {  
  10.            sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);  
  11.            prepareDispatchCycleLocked(currentTime, connection, eventEntry, & inputTarget,  
  12.                resumeWithAppendedMotionSample);  
  13.        } else {  
  14.            ......  
  15.    }  
  16. }  
        这个函数的实现也比较简单,前面我们已经把当前需要接受键盘事件的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. void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,  
  2.         const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget,  
  3.         bool resumeWithAppendedMotionSample) {  
  4.      ......  
  5.   
  6.   
  7.      // Resume the dispatch cycle with a freshly appended motion sample.  
  8.      // First we check that the last dispatch entry in the outbound queue is for the same  
  9.      // motion event to which we appended the motion sample.  If we find such a dispatch  
  10.      // entry, and if it is currently in progress then we try to stream the new sample.  
  11.      bool wasEmpty = connection->outboundQueue.isEmpty();  
  12.   
  13.      if (! wasEmpty && resumeWithAppendedMotionSample) {  
  14.          ......  
  15.          return;  
  16.      }  
  17.   
  18.      // This is a new event.  
  19.      // Enqueue a new dispatch entry onto the outbound queue for this connection.  
  20.      DispatchEntry* dispatchEntry = mAllocator.obtainDispatchEntry(eventEntry, // increments ref  
  21.          inputTarget->flags, inputTarget->xOffset, inputTarget->yOffset);  
  22.   
  23.      ......  
  24.   
  25.      // Enqueue the dispatch entry.  
  26.      connection->outboundQueue.enqueueAtTail(dispatchEntry);  
  27.   
  28.      // If the outbound queue was previously empty, start the dispatch cycle going.  
  29.      if (wasEmpty) {  
  30.          ......  
  31.   
  32.          startDispatchCycleLocked(currentTime, connection);  
  33.      }  
  34. }  

         在开始处理键盘事件之前,这个函数会检查一下传进来的参数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. void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,  
  2.     const sp<Connection>& connection) {  
  3.     ......  
  4.   
  5.     DispatchEntry* dispatchEntry = connection->outboundQueue.headSentinel.next;  
  6.   
  7.     // Mark the dispatch entry as in progress.  
  8.     dispatchEntry->inProgress = true;  
  9.   
  10.     // Update the connection's input state.  
  11.     EventEntry* eventEntry = dispatchEntry->eventEntry;  
  12.     ......  
  13.   
  14.     // Publish the event.  
  15.     status_t status;  
  16.     switch (eventEntry->type) {  
  17.     case EventEntry::TYPE_KEY: {  
  18.         KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);  
  19.   
  20.         // Apply target flags.  
  21.         int32_t action = keyEntry->action;  
  22.         int32_t flags = keyEntry->flags;  
  23.   
  24.         // Publish the key event.  
  25.         status = connection->inputPublisher.publishKeyEvent(keyEntry->deviceId, keyEntry->source,  
  26.             action, flags, keyEntry->keyCode, keyEntry->scanCode,  
  27.             keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime,  
  28.             keyEntry->eventTime);  
  29.   
  30.         ......  
  31.         break;  
  32.     }  
  33.     ......  
  34.     }  
  35.   
  36.     // Send the dispatch signal.  
  37.     status = connection->inputPublisher.sendDispatchSignal();  
  38.       
  39.     ......  
  40. }  
         这个函数主要围绕传进来的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_t InputPublisher::publishKeyEvent(  
  2.     int32_t deviceId,  
  3.     int32_t source,  
  4.     int32_t action,  
  5.     int32_t flags,  
  6.     int32_t keyCode,  
  7.     int32_t scanCode,  
  8.     int32_t metaState,  
  9.     int32_t repeatCount,  
  10.     nsecs_t downTime,  
  11.     nsecs_t eventTime) {  
  12.     ......  
  13.   
  14.     status_t result = publishInputEvent(AINPUT_EVENT_TYPE_KEY, deviceId, source);  
  15.     if (result < 0) {  
  16.         return result;  
  17.     }  
  18.   
  19.     mSharedMessage->key.action = action;  
  20.     mSharedMessage->key.flags = flags;  
  21.     mSharedMessage->key.keyCode = keyCode;  
  22.     mSharedMessage->key.scanCode = scanCode;  
  23.     mSharedMessage->key.metaState = metaState;  
  24.     mSharedMessage->key.repeatCount = repeatCount;  
  25.     mSharedMessage->key.downTime = downTime;  
  26.     mSharedMessage->key.eventTime = eventTime;  
  27.     return OK;  
  28. }  
        这个函数主要就是把键盘事件记录在InputPublisher类的成员变量mSharedMessage中了,这个mSharedMessage成员变量指向的是一个匿名共享内存。

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

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

view plain
  1. status_t InputPublisher::initialize() {  
  2.     ......  
  3.   
  4.     int ashmemFd = mChannel->getAshmemFd();  
  5.     int result = ashmem_get_size_region(ashmemFd);  
  6.     ......  
  7.   
  8.     mAshmemSize = (size_t) result;  
  9.   
  10.     mSharedMessage = static_cast<InputMessage*>(mmap(NULL, mAshmemSize,  
  11.         PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0));  
  12.     ......  
  13.   
  14.     mPinned = true;  
  15.     mSharedMessage->consumed = false;  
  16.   
  17.     return reset();  
  18. }  
        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_t InputPublisher::sendDispatchSignal() {  
  2.     ......  
  3.   
  4.     mWasDispatched = true;  
  5.     return mChannel->sendSignal(INPUT_SIGNAL_DISPATCH);  
  6. }  
        这个函数很简单,它通过调用内部成员变量mChannel的sendSignal函数来通知相应的Activity窗口来处理键盘事件。

        Step 20. InputChannel.sendSignal

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

view plain
  1. status_t InputChannel::sendSignal(char signal) {  
  2.     ssize_t nWrite;  
  3.     do {  
  4.         nWrite = ::write(mSendPipeFd, & signal, 1);  
  5.     } while (nWrite == -1 && errno == EINTR);  
  6.   
  7.     if (nWrite == 1) {  
  8.         ......  
  9.         return OK;  
  10.     }  
  11.   
  12.     return -errno;  
  13. }  
        这里所谓的发送信号通知,其实是通过向其内部一个管道的写端写入一个字符来实现的。前面我们分析应用程序注册键盘消息接收通道的过程时,在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. int NativeInputQueue::handleReceiveCallback(int receiveFd, int events, void* data) {  
  2.     NativeInputQueue* q = static_cast<NativeInputQueue*>(data);  
  3.     JNIEnv* env = AndroidRuntime::getJNIEnv();  
  4.   
  5.     sp<Connection> connection;  
  6.     InputEvent* inputEvent;  
  7.     jobject inputHandlerObjLocal;  
  8.     jlong finishedToken;  
  9.     { // acquire lock  
  10.         AutoMutex _l(q->mLock);  
  11.   
  12.         ssize_t connectionIndex = q->mConnectionsByReceiveFd.indexOfKey(receiveFd);  
  13.         ......  
  14.   
  15.             connection = q->mConnectionsByReceiveFd.valueAt(connectionIndex);  
  16.         ......  
  17.   
  18.   
  19.             status_t status = connection->inputConsumer.receiveDispatchSignal();  
  20.         if (status) {  
  21.             ......  
  22.                 return 0; // remove the callback  
  23.         }  
  24.   
  25.         ......  
  26.   
  27.         status = connection->inputConsumer.consume(& connection->inputEventFactory, & inputEvent);  
  28.         ......  
  29.   
  30.         finishedToken = generateFinishedToken(receiveFd, connection->id, connection->messageSeqNum);  
  31.   
  32.         inputHandlerObjLocal = env->NewLocalRef(connection->inputHandlerObjGlobal);  
  33.     } // release lock  
  34.   
  35.     ......  
  36.   
  37.     int32_t inputEventType = inputEvent->getType();  
  38.   
  39.     jobject inputEventObj;  
  40.     jmethodID dispatchMethodId;  
  41.     switch (inputEventType) {  
  42.     case AINPUT_EVENT_TYPE_KEY:  
  43.         ......  
  44.             inputEventObj = android_view_KeyEvent_fromNative(env,  
  45.             static_cast<KeyEvent*>(inputEvent));  
  46.         dispatchMethodId = gInputQueueClassInfo.dispatchKeyEvent;  
  47.         break;  
  48.     }  
  49.     ......  
  50.     }  
  51.   
  52.     ......  
  53.   
  54.     env->CallStaticVoidMethod(gInputQueueClassInfo.clazz,  
  55.                           dispatchMethodId, inputHandlerObjLocal, inputEventObj,  
  56.                           jlong(finishedToken));  
  57.   
  58.     ......  
  59.   
  60.     return 1;  
  61. }  

        这个函数首先是通过参数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_t InputConsumer::receiveDispatchSignal() {  
  2.     ......  
  3.   
  4.     char signal;  
  5.     status_t result = mChannel->receiveSignal(& signal);  
  6.     if (result) {  
  7.         return result;  
  8.     }  
  9.     if (signal != INPUT_SIGNAL_DISPATCH) {  
  10.         ......  
  11.         return UNKNOWN_ERROR;  
  12.     }  
  13.     return OK;  
  14. }  
         这个函数很简单,它通过它内部对象mChannel来从前向管道的读端读入一个字符,看看是否是前面的Step 20中写入的INPUT_SIGNAL_DISPATCH字符。

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

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

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

view plain
  1. status_t InputConsumer::consume(InputEventFactoryInterface* factory, InputEvent** outEvent) {  
  2.     ......  
  3.   
  4.     *outEvent = NULL;  
  5.   
  6.     int ashmemFd = mChannel->getAshmemFd();  
  7.     int result = ashmem_pin_region(ashmemFd, 0, 0);  
  8.     ......  
  9.   
  10.     if (mSharedMessage->consumed) {  
  11.         ......  
  12.         return INVALID_OPERATION;  
  13.     }  
  14.   
  15.     // Acquire but *never release* the semaphore.  Contention on the semaphore is used to signal  
  16.     // to the publisher that the message has been consumed (or is in the process of being  
  17.     // consumed).  Eventually the publisher will reinitialize the semaphore for the next message.  
  18.     result = sem_wait(& mSharedMessage->semaphore);  
  19.     ......  
  20.   
  21.     mSharedMessage->consumed = true;  
  22.   
  23.     switch (mSharedMessage->type) {  
  24.     case AINPUT_EVENT_TYPE_KEY: {  
  25.         KeyEvent* keyEvent = factory->createKeyEvent();  
  26.         if (! keyEvent) return NO_MEMORY;  

你可能感兴趣的:(Android应用程序键盘(Keyboard)消息处理机制分析(三))