在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
點選(此處)折疊或打開
- class ServerThread extends Thread {
- //省略。。
- public void run() {
- // Create a handler thread just for the window manager to enjoy.
- HandlerThread wmHandlerThread = new HandlerThread("WindowManager");
- wmHandlerThread.start();
- Handler wmHandler = new Handler(wmHandlerThread.getLooper());
- //此處省略5k字。。
- Slog.i(TAG, "Input Manager");
- inputManager = new InputManagerService(context, wmHandler);
- }
- }
可以看到,在系統啟動的時候,會首先建立一個系統級别的Handler線程wmHandlerThread用于處理鍵盤消息(僅說明鍵盤消息)。然後在建立輸入管理服務inputManager,InputManagerService 的第二個參數就是用于處理按鍵消息的Handler。
Step 2
在往下走到 InputManagerService.java的構造函數。
點選(此處)折疊或打開
- public InputManagerService(Context context, Handler handler) {
- this.mContext = context;
- this.mHandler = new InputManagerHandler(handler.getLooper());
- mUseDevInputEventForAudioJack =
- context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
- Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="
- + mUseDevInputEventForAudioJack);
- mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
- }
這裡做了重要的兩件事情,第一:将SystemServer級别的Handler指派給 InputManagerService自己的消息處理Handler;第二:調用nativeInit繼續進行初始化。
Step 3
com_android_server_ InputManagerService.cpp
點選(此處)折疊或打開
- static jint nativeInit(JNIEnv* env, jclass clazz,
- jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
- sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
- NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
- messageQueue->getLooper());
- im->incStrong(serviceObj);
- return reinterpret_cast<jint>(im);
- }
這裡nativeInit直接調用了 NativeInputManager的構造函數
Step 4
點選(此處)折疊或打開
- NativeInputManager::NativeInputManager(jobject contextObj,
- jobject serviceObj, const sp<Looper>& looper) :
- mLooper(looper) {
- JNIEnv* env = jniEnv();
- mContextObj = env->NewGlobalRef(contextObj);
- mServiceObj = env->NewGlobalRef(serviceObj);
- {
- AutoMutex _l(mLock);
- mLocked.systemUiVisibility = ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE;
- mLocked.pointerSpeed = 0;
- mLocked.pointerGesturesEnabled = true;
- mLocked.showTouches = false;
- }
- sp<EventHub> eventHub = new EventHub();
- mInputManager = new InputManager(eventHub, this, this);
- }
這裡需要特别注意最後兩行代碼。第一:建立了 EventHub;第二:建立 InputManager并将 EventHub作為參數傳入InputManager。
Step 5
接下來繼續看看InputManager的構造函數。
點選(此處)折疊或打開
- InputManager::InputManager(
- const sp<EventHubInterface>& eventHub,
- const sp<InputReaderPolicyInterface>& readerPolicy,
- const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
- mDispatcher = new InputDispatcher(dispatcherPolicy);
- mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
- initialize();
- }
- void InputManager::initialize() {
- mReaderThread = new InputReaderThread(mReader);
- mDispatcherThread = new InputDispatcherThread(mDispatcher);
- }
建立了InputDispatcher 和InputReader ,并調用了initialize函數建立了InputReaderThread和InputDispatcherThread。InputDispatcher類是負責把鍵盤消息分發給目前激活的Activity視窗的,而InputReader類則是通過 EventHub類來實作讀取鍵盤事件的,InputReader實列mReader就是通過這裡的 InputReaderThread線程實列mReaderThread來讀取鍵盤事件的,而InputDispatcher執行個體mDispatcher 則是通過這裡的InputDispatcherThread線程執行個體mDisptacherThread來分發鍵盤消息的。
到這裡,相關的元件都已經被建立了;
Step 6
接下來看看他們是如何運作起來的。
在systemServer.java中建立inputManager之後。将InputManagerServer進行注冊,并運作start()
點選(此處)折疊或打開
- ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
- inputManager.start();
- //InputManager的start函數:
- public void start() {
- Slog.i(TAG, "Starting input manager");
- nativeStart(mPtr);
- //省略。。
- }
調用nativeStart繼續往下走。順帶說一下,這裡的參數mPtr是指向native inputmanager service對象的,在InputManagerService構造函數中由nativeInit指派。
Step 7
接下來又到了com_android_server_ InputManagerService.cpp中。
點選(此處)折疊或打開
- static void nativeStart(JNIEnv* env, jclass clazz, jint ptr) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
- status_t result = im->getInputManager()->start();
- if (result) {
- jniThrowRuntimeException(env, "Input manager could not be started.");
- }
- }
這裡的im就是inputManager并且用到了上面傳下來的mPtr來重新建構。
Step 8
繼續往下則會調用到InputManager.cpp 的start函數
點選(此處)折疊或打開
- status_t InputManager::start() {
- status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
- if (result) {
- ALOGE("Could not start InputDispatcher thread due to error %d.", result);
- return result;
- }
- result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
- if (result) {
- ALOGE("Could not start InputReader thread due to error %d.", result);
- mDispatcherThread->requestExit();
- return result;
- }
- return OK;
- }
這個函數主要就是分别啟動一個InputDispatcherThread線程和一個InputReaderThread線程來讀取和分發鍵 盤消息的了。這裡的InputDispatcherThread線程對象mDispatcherThread和InputReaderThread線程對 象是在前面的Step 9中建立的,調用了它們的run函數後,就會進入到它們的threadLoop函數中去,隻要threadLoop函數傳回true,函數 threadLoop就會一直被循環調用,于是這兩個線程就起到了不斷地讀取和分發鍵盤消息的作用。
Step 9
在下來繼續看loopOnce()這個函數。
點選(此處)折疊或打開
- void InputReader::loopOnce() {
- //......
- size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
- //......
- if (count) {
- processEventsLocked(mEventBuffer, count);
- }
- //......
- // Flush queued events out to the listener.
- // This must happen outside of the lock because the listener could potentially call
- // back into the InputReader's methods, such as getScanCodeState, or become blocked
- // on another thread similarly waiting to acquire the InputReader lock thereby
- // resulting in a deadlock. This situation is actually quite plausible because the
- // listener is actually the input dispatcher, which calls into the window manager,
- // which occasionally calls into the input reader.
- mQueuedListener->flush();
- }
這裡面需要注意像神一樣的函數 mEventHub->getEvents()。其實作原理,還有點不是很清楚;但是其功能就是負責鍵盤消息的讀取工作,如果目前有鍵盤事件發生或者有鍵盤事件等待處理,通過mEventHub的 getEvent函數就可以得到這個事件,然後交給processEventsLocked 函數進行處理。同樣需要特别注意最後一行;後面回解釋。我們還會回來的~~~
點選(此處)折疊或打開
- /*
- * Wait for events to become available and returns them.
- * After returning, the EventHub holds onto a wake lock until the next call to getEvent.
- * This ensures that the device will not go to sleep while the event is being processed.
- * If the device needs to remain awake longer than that, then the caller is responsible
- * for taking care of it (say, by poking the power manager user activity timer).
- *
- * The timeout is advisory only. If the device is asleep, it will not wake just to
- * service the timeout.
- *
- * Returns the number of events obtained, or 0 if the timeout expired.
- */
- 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函數。
點選(此處)折疊或打開
- void InputDevice::process(const RawEvent* rawEvents, size_t count) {
- //。。。。
- InputMapper* mapper = mMappers[i];
- mapper->process(rawEvent);
- }
走到這裡才算是真真正正的知道了有按鍵發生了,調用 KeyboardInputMapper::process(const RawEvent*)處理input event; KeyboardInputMapper 繼承自 InputMapper。那為什麼調用的是KeyboardInputMapper而不是SwitchInputMapper等等。。
請留意
點選(此處)折疊或打開
- InputDevice* InputReader::createDeviceLocked(int32_t deviceId,
- const InputDeviceIdentifier& identifier, uint32_t classes)
函數中的片段:
點選(此處)折疊或打開
- if (keyboardSource != 0) {
- device->addMapper(new KeyboardInputMapper(device, keyboardSource, keyboardType));
- }
這裡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
點選(此處)折疊或打開
- void KeyboardInputMapper::process(const RawEvent* rawEvent) {
- switch (rawEvent->type) {
- case EV_KEY: {
- int32_t scanCode = rawEvent->code;
- int32_t usageCode = mCurrentHidUsage;
- mCurrentHidUsage = 0;
- if (isKeyboardOrGamepadKey(scanCode)) {
- int32_t keyCode;
- uint32_t flags;
- if (getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, &keyCode, &flags)) {
- keyCode = AKEYCODE_UNKNOWN;
- flags = 0;
- }
- processKey(rawEvent->when, rawEvent->value != 0, keyCode, scanCode, flags);
- }
- break;
- }
- }
- }
在這裡,先判斷isKeyboardOrGamepadKey(scanCode),然後在用getEventHub()->mapKey()檢測 提供的key是否正确,在然後就開始處理了processKey
Step 12
點選(此處)折疊或打開
- void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode,
- int32_t scanCode, uint32_t policyFlags) {
- //忽略到所有的。。隻看最後兩行。。
- NotifyKeyArgs args(when, getDeviceId(), mSource, policyFlags,
- down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
- AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, newMetaState, downTime);
- getListener()->notifyKey(&args);
- }
不用多解釋了,直接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的映射值。
到最後了啊。。。
呵呵,看看是怎麼實作的。。