zoukankan      html  css  js  c++  java
  • (转) Android 按键消息处理

    http://blog.chinaunix.net/uid-26281173-id-3506751.html   

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

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



    1
    kernel中同按键相关代码

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

     

     

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

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

     

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

    2framework针对键盘事件的处理

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

        首先来看看InputManagerService的创建,

    Step 1

    在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。

     

    Step 2

    在往下走到 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继续进行初始化。

     

    Step 3

    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的构造函数

     

    Step 4

    点击(此处)折叠或打开

    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。

     

    Step 5

    接下来继续看看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来分发键盘消息的。

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

     

    Step 6

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

    在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是指向native inputmanager service对象的,在InputManagerService构造函数中由nativeInit赋值。

     

    Step 7

    接下来又到了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来重新构建。

     

    Step 8

    继续往下则会调用到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线程对 象是在前面的Step 9中创建的,调用了它们的run函数后,就会进入到它们的threadLoop函数中去,只要threadLoop函数返回true,函数 threadLoop就会一直被循环调用,于是这两个线程就起到了不断地读取和分发键盘消息的作用。

     

    Step 9

    在下来继续看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(const RawEvent* 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. Events may be separated in time or in space, such as with the multitouch protocol.

    * EV_KEY:

      - Used to describe state changes of keyboards, 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 value changes, e.g. describing the coordinates of a touch on a touchscreen.

    * EV_MSC:

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

    * EV_SW:

    -           Used to describe binary state input 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映射是在函数bool TouchInputMapper::consumeRawTouches(nsecs_t when, uint32_t policyFlags)  中。这里同上面的Step 12相同。

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





  • 相关阅读:
    HDU 5492 Find a path
    codeforce gym 100548H The Problem to Make You Happy
    Topcoder SRM 144 Lottery
    codeforce 165E Compatible Numbers
    codeforce gym 100307H Hack Protection
    区间DP总结
    UESTC 1321 柱爷的恋爱 (区间DP)
    HDU 4283 You Are the One (区间DP)
    HDU 2476 String painter (区间DP)
    UESTC 426 Food Delivery (区间DP)
  • 原文地址:https://www.cnblogs.com/fireflyxml/p/3577708.html
Copyright © 2011-2022 走看看