1. 程式人生 > >Android 按鍵訊息處理

Android 按鍵訊息處理

Android按鍵訊息處理

    在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是屬於驅動範疇,不討論。

2、framework針對鍵盤事件的處理

    上層對輸入事件的偵聽和分發是在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的對映值。
到最後了啊。。。
呵呵,看看是怎麼實現的。。