现在的位置: 首页 > 综合 > 正文

Android按键消息处理

2013年10月11日 ⁄ 综合 ⁄ 共 10276字 ⁄ 字号 评论关闭

在android系统中,键盘按键事件是由SystemServer服务来管理的;然后在以消息的形式分发给应用程序处理。产生键盘按键事件则是有Linuxkernel的相关驱动来实现。

键盘消息有别于其他类型的消息;需要从Linuxkerneldrivers产生由上层app来处理。同时按键有着不同的映射值,因此从模块独立性角度各个独立的模块应该拥有不同的键盘映射。这样以来,kernel产生的按键事件必然回经过不同的映射才到app。



1、kernel中同按键相关代码

    Android 使用标准的 linux 输入事件设备(/dev/input/)和驱动按键定义在 linux 内核include/linux/input.h 中,按键的定义形式如下(仅以BACKHOME MENU为例):


    有了按键的定义,就需要产生相应的按键事件了。在kernel/arch/arm/mach-msm/xxx/xxx/xxx.c会对BACK HOME和MENU进行注册。这里使用在屏幕上的坐标来对按键进行区分。这部分代码会在系统启动的时候,将相应的数据存储,以供framework查询。

(这里以xxx代替,是因为针对不同的硬件,需要的Linux kernel不同)


当然从核心板原理图到kernel是属于驱动范畴,不讨论。

2、framework针对键盘事件的处理

    上层对输入事件的侦听和分发是在InputManagerService 中实现

    首先来看看InputManagerService的创建,

Step1

在SystemServer.java

点击(此处)折叠或打开

  1. class ServerThread
    extends
    Thread {
  2.     //省略。。
  3.     public
    void
    run()
    {
  4.         // Create a handler thread just for the window manager to enjoy.
  5.         HandlerThread wmHandlerThread
    =
    new HandlerThread("WindowManager");
  6.         wmHandlerThread.start();
  7.         Handler wmHandler
    = new
    Handler
    (wmHandlerThread.getLooper());
  8.         //此处省略5k字。。
  9.         Slog.i(TAG,
    "Input Manager");
  10.         inputManager
    =
    new InputManagerService(context, wmHandler);
  11.     }
  12. }

可以看到,在系统启动的时候,会首先创建一个系统级别的Handler线程wmHandlerThread用于处理键盘消息(仅说明键盘消息)。然后在创建输入管理服务 inputManager,InputManagerService 的第二个参数就是用于处理按键消息的Handler。


Step2

在往下走到 InputManagerService.java的构造函数。

点击(此处)折叠或打开

  1. public InputManagerService(Context
    context,
    Handler
    handler)
    {
  2.     this.mContext
    = context;
  3.     this.mHandler
    = new InputManagerHandler(handler.getLooper());

  4.     mUseDevInputEventForAudioJack
    =

  5.                 context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
  6.     Slog.i(TAG,
    "Initializing input manager, mUseDevInputEventForAudioJack="
  7.                     + mUseDevInputEventForAudioJack);
  8.     mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
  9. }

这里做了重要的两件事情,第一:将SystemServer级别的Handler赋值给 InputManagerService自己的消息处理Handler;第二:调用nativeInit继续进行初始化。


Step3

com_android_server_InputManagerService.cpp

点击(此处)折叠或打开

  1. static jint nativeInit(JNIEnv* env, jclass clazz,
  2.     jobject serviceObj, jobject contextObj, jobject messageQueueObj)
    {
  3.     sp<MessageQueue> messageQueue
    = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
  4.     NativeInputManager* im
    = new NativeInputManager(contextObj, serviceObj,
  5.     messageQueue->getLooper());
  6.     im->incStrong(serviceObj);
  7.     return reinterpret_cast<jint>(im);
  8. }

这里nativeInit直接调用了 NativeInputManager的构造函数


Step4

点击(此处)折叠或打开

  1. NativeInputManager::NativeInputManager(jobject
    contextObj,
  2.     jobject serviceObj,
    const sp<Looper>& looper)
    :
  3.     mLooper(looper)
    {
  4.     JNIEnv* env
    = jniEnv();

  5.     mContextObj = env->NewGlobalRef(contextObj);
  6.     mServiceObj = env->NewGlobalRef(serviceObj);

  7.     {
  8.         AutoMutex _l(mLock);
  9.         mLocked.systemUiVisibility
    = ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE;
  10.         mLocked.pointerSpeed
    = 0;
  11.         mLocked.pointerGesturesEnabled
    = true;
  12.         mLocked.showTouches
    = false;
  13.     }

  14.     sp<EventHub> eventHub
    = new EventHub();
  15.     mInputManager = new InputManager(eventHub, this, this);
  16. }

这里需要特别注意最后两行代码。第一:创建了 EventHub;第二:创建 InputManager并将 EventHub作为参数传入InputManager。


Step5

接下来继续看看InputManager的构造函数。

点击(此处)折叠或打开

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

  9. void InputManager::initialize()
    {
  10.     mReaderThread = new InputReaderThread(mReader);
  11.     mDispatcherThread
    =
    new InputDispatcherThread(mDispatcher);
  12. }



创建了InputDispatcher 和InputReader,并调用了initialize函数创建了InputReaderThread和InputDispatcherThread。InputDispatcher类是负责把键盘消息分发给当前激活的Activity窗口的,而InputReader类则是通过 EventHub类来实现读取键盘事件的,InputReader实列mReader就是通过这里的 InputReaderThread线程实列mReaderThread来读取键盘事件的,而InputDispatcher实例mDispatcher
则是通过这里的InputDispatcherThread线程实例mDisptacherThread来分发键盘消息的。

到这里,相关的组件都已经被创建了;


Step6

接下来看看他们是如何运行起来的。

在systemServer.java中创建inputManager之后。将InputManagerServer进行注册,并运行start()

点击(此处)折叠或打开

  1. ServiceManager.addService(Context.INPUT_SERVICE,
    inputManager);
  2.     inputManager.start();
  3.     //InputManager的start函数:
  4.     public
    void
    start()
    {
  5.     Slog.i(TAG,
    "Starting input manager");
  6.     nativeStart(mPtr);
  7.     //省略。。
  8. }

调用nativeStart继续往下走。顺带说一下,这里的参数mPtr是指向nativeinputmanager service对象的,在InputManagerService构造函数中由nativeInit赋值。


Step7

接下来又到了com_android_server_InputManagerService.cpp中。

点击(此处)折叠或打开

  1. static void nativeStart(JNIEnv* env, jclass clazz,
    jint ptr) {
  2.     NativeInputManager* im
    = reinterpret_cast<NativeInputManager*>(ptr);

  3.     status_t result
    =
    im->getInputManager()->start();
  4.     if
    (
    result) {
  5.         jniThrowRuntimeException(env,
    "Input manager could not be started.");
  6.     }
  7. }

这里的im就是inputManager并且用到了上面传下来的mPtr来重新构建。


Step8

继续往下则会调用到InputManager.cpp 的start函数

点击(此处)折叠或打开

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

  7.     result = mReaderThread->run("InputReader",
    PRIORITY_URGENT_DISPLAY);
  8.     if
    (
    result) {
  9.         ALOGE("Could not start InputReader thread due to error %d.", result);

  10.         mDispatcherThread->requestExit();
  11.         return result;
  12.     }

  13.     return OK;
  14. }

这个函数主要就是分别启动一个InputDispatcherThread线程和一个InputReaderThread线程来读取和分发键 盘消息的了。这里的InputDispatcherThread线程对象mDispatcherThread和InputReaderThread线程对 象是在前面的Step9中创建的,调用了它们的run函数后,就会进入到它们的threadLoop函数中去,只要threadLoop函数返回true,函数 threadLoop就会一直被循环调用,于是这两个线程就起到了不断地读取和分发键盘消息的作用。


Step9

在下来继续看loopOnce()这个函数。

点击(此处)折叠或打开

  1. void InputReader::loopOnce()
    {
  2.     //......
  3.     size_t count = mEventHub->getEvents(timeoutMillis,
    mEventBuffer, EVENT_BUFFER_SIZE);
  4.     //......
  5.     if
    (
    count) {
  6.         processEventsLocked(mEventBuffer, count);
  7.     }
  8.     //......
  9.     // Flush queued events out
    to the listener.
  10.     // This must happen outside of the lock because the listener could potentially
    call
  11.     // back into the InputReader's methods, such as getScanCodeState,
    or become blocked
  12.     //
    on another thread similarly waiting
    to
    acquire the InputReader lock thereby
  13.     // resulting
    in a deadlock. This situation
    is actually quite plausible because the
  14.     // listener
    is actually the input dispatcher, which calls into the
    window manager,
  15.     // which occasionally calls into the input reader.
  16.     mQueuedListener->flush();
  17. }

这里面需要注意像神一样的函数 mEventHub->getEvents()。其实现原理,还有点不是很清楚;但是其功能就是负责键盘消息的读取工作,如果当前有键盘事件发生或者有键盘事件等待处理,通过mEventHub的 getEvent函数就可以得到这个事件,然后交给processEventsLocked 函数进行处理。同样需要特别注意最后一行;后面回解释。我们还会回来的~~~


点击(此处)折叠或打开

  1. /*
  2.      * Wait
    for
    events to become available
    and
    returns them.
  3.      * After returning, the EventHub holds onto a wake lock
    until the next
    call to getEvent.
  4.      * This ensures that the device will
    not go to sleep
    while the event is being processed.
  5.      *
    If
    the device needs to remain awake longer than that,
    then the caller is responsible
  6.      *
    for
    taking care of it (say, by poking the power manager user activity timer).
  7.      *
  8.      * The timeout
    is advisory only.
    If the device is asleep, it will
    not wake just to
  9.      * service the timeout.
  10.      *
  11.      * Returns the number of events obtained,
    or 0 if the timeout expired.
  12.      */
  13.     virtual size_t getEvents(int timeoutMillis, RawEvent* buffer,
    size_t bufferSize)

函数原型!

在成功获取input Event之后,就会用到 processEventsLocked函数来处理Event

然后在调用到 processEventsForDeviceLocked(deviceId,rawEvent, batchSize);

最后在void InputDevice::process(constRawEvent* rawEvents, size_t count)

我就在想:问什么不直接到process函数呢?其实我觉得这里体现了设计模式中的单一职责原则;这种设计可以有效的控制函数粒度(有个类粒度,这里自创函数粒度)的大小,函数承担的职责越多其复用的可能性就越小,并且当期中某一个职责发生变化,可能会影响其他职责的运作!


Step 10

接下来继续看InputDevice::process函数。

点击(此处)折叠或打开

  1. void InputDevice::process(const RawEvent*
    rawEvents, size_t count)
    {
  2.     //。。。。
  3.     InputMapper* mapper
    = mMappers[i];
  4.     mapper->process(rawEvent);

  5. }

走到这里才算是真真正正的知道了有按键发生了,调用 KeyboardInputMapper::process(const RawEvent*)处理input event; KeyboardInputMapper 继承自 InputMapper。那为什么调用的是 KeyboardInputMapper而不是SwitchInputMapper等等。。

请留意

点击(此处)折叠或打开

  1. InputDevice* InputReader::createDeviceLocked(int32_t deviceId,
  2.                                                 const InputDeviceIdentifier& identifier, uint32_t classes)

函数中的片段:

点击(此处)折叠或打开

  1. if
    (keyboardSource != 0)
    {
  2.         device->addMapper(new KeyboardInputMapper(device,
    keyboardSource, keyboardType));
  3.     }

这里Event Type有必要提一下,以下是一些常用的Event。在kernel/Documentation/input/event-codes.txt中有详细的描述。

* EV_SYN:

  - Used as markers to separate events. Eventsmay be separated in time or in space, such as with the multitouch protocol.

* EV_KEY:

  - Used to describe state changes ofkeyboards, buttons, or other key-like devices.

* EV_REL:

  - Used to describe relative axis value changes,e.g. moving the mouse 5 units to the left.

* EV_ABS:

  - Used to describe absolute axis valuechanges, e.g. describing the coordinates of a touch on a touchscreen.

* EV_MSC:

  - Used to describe miscellaneous input datathat do not fit into other types.

* EV_SW:

-           Used to describe binary stateinput switches.

Step 11

点击(此处)折叠或打开


  1. void KeyboardInputMapper::process(const RawEvent*
    rawEvent) {
  2.     switch (rawEvent->type)
    {
  3.     case EV_KEY:
    {
  4.         int32_t scanCode
    =
    rawEvent->code;
  5.         int32_t usageCode
    =
    mCurrentHidUsage;
  6.         mCurrentHidUsage
    =
    0;

  7.         if
    (
    isKeyboardOrGamepadKey(scanCode))
    {
  8.             int32_t keyCode;
  9.             uint32_t flags;
  10.             if
    (getEventHub()->mapKey(getDeviceId(),
    scanCode, usageCode,
    &keyCode,
    &
    flags))
    {
  11.                 keyCode
    =
    AKEYCODE_UNKNOWN;
  12.                 flags
    =
    0;
  13.             }
  14.             processKey(rawEvent->when, rawEvent->value
    != 0, keyCode, scanCode, flags);
  15.         }
  16.         break;
  17.     }
  18.     }
  19. }

在这里,先判断isKeyboardOrGamepadKey(scanCode),然后在用getEventHub()->mapKey()检测 提供的key是否正确,在然后就开始处理了processKey

Step 12

点击(此处)折叠或打开

  1. void KeyboardInputMapper::processKey(nsecs_t when, bool down,
    int32_t keyCode,
  2.         int32_t scanCode, uint32_t policyFlags)
    {
  3.     //忽略到所有的。。只看最后两行。。
  4.     NotifyKeyArgs args(when, getDeviceId(), mSource,
    policyFlags,
  5.         down ? AKEY_EVENT_ACTION_DOWN
    : AKEY_EVENT_ACTION_UP,
  6.          AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, newMetaState, downTime);

  7.      getListener()->notifyKey(&args);
  8. }


    不用多解释了,直接notifyKey了。。但需要注意,这里的notifyKey 仅仅是 NotifyKeyArgs  push到消息队列中去;并没有通知上层!那到底在那儿通知的呢?


还记不记得在void InputReader::loopOnce()这个函数的最后一行代码,其实质是在这个函数中通知上层有按键事件发生。

这个flush()很明显,notify了之后,就delete,不存在了。问什么不是在getListener()->notifyKey(&args);的时候就真正的notify?我觉得可以做如下角度予以考虑:


第一:线程是最小的执行单位;因此每当inputThread.start()的时候,如果不flush,回造成数据混乱。

第二:flush操作是必须的,同时在loopOnce的最后操作也是最恰当的。其实这里的Listener也就是充当了一个事件分发者的角色。

这说明,到这里已经完全识别了按键了,并按照自己的键盘映射映射了一个值保存在args中,notifyKey给上层应用了。。


Step 13

         其实针对BACK  HOME MENU这三个按键来说,其实质就是TouchScreen;因此在inputReader.cpp中获取Touch映射是在函数boolTouchInputMapper::consumeRawTouches(nsecs_t when, uint32_t policyFlags)  中。这里同上面的Step 12相同。


首先检测不是多点Touch。然后使用const TouchInputMapper::VirtualKey*TouchInputMapper::findVirtualKeyHit( int32_t x, int32_t y)依据坐标值查找出Touch的映射值。
到最后了啊。。。
呵呵,看看是怎么实现的。。

抱歉!评论已关闭.