Android 的 KeyEvent : 從 EventHub 到 PhoneWindowManager
最近追蹤了一下 Android 4.3 的 source,並且追蹤了 KeyEvent 一路從 EventHub.cpp 到 PhoneWondowManager.java 的流程 這邊順便記錄一下。
Android 在 Linux kernal 的部份,所有的 Input Event 都會使用 /dev/input/event0~X 的 device node 要 Monitoring Input Event,基本上可以去 pulling 這幾個 node (但是拿到的會是 RawEvent Data,而且應該會是顯示亂碼)
或是可以利用 adb shell getevent
的指令,一來他會列出這個 node 現在使用的 device name 是什麼 (例如鍵盤、gpio-key或是 touch screen等)也會把 Raw Event 轉換成比較容易了解的格式,以及數字,在 Debug 上也蠻好用的。
切入正題,目前看到 Android 4.3 KeyEvent 的流程大致上為:
EventHub.cpp 基本上去監控 /dev/input/eventX 的 node,但是只是提供 function 來讓外部存取。
一開始是由 frameworks/base/service/input/InputManager.cpp
中會建立 InputReaderThread
並且在初始化完成之後開始 run。
接下來的 code 就會跑到 frameworks/base/service/input/InputReader.cpp
裡面
這個 Thread 基本上就是去做 pulling 的動作,並且實作在 loopOnce 這個 function
void InputReader::loopOnce() { ... size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE); ... processEventsLocked(mEventBuffer, count); ... }
在這個 function 裡面會去呼叫到 mEventHub->getEvents() 並取得最原始的 RawEvent 接著會丟到 processEventsLocked() 然後 processEventsForDeviceLocked()
void InputReader::processEventsForDeviceLocked(int32_t deviceId, const RawEvent* rawEvents, size_t count) { ssize_t deviceIndex = mDevices.indexOfKey(deviceId); ... InputDevice* device = mDevices.valueAt(deviceIndex); ... device->process(rawEvents, count); } void InputDevice::process(const RawEvent* rawEvents, size_t count) { ... for (size_t i = 0; i < numMappers; i++) { InputMapper* mapper = mMappers[i]; mapper->process(rawEvent); } } 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); }
接著會根據目前 Event 的 device 種類,丟到 InputDevice 的 process() 再來會把這個 RawEvent 丟給所有的 InputMapper 的 process() 看誰要處理 以 KeyEvent 來說,會處理的會是 KeyboardInputMapper (一樣位於 InputReader.cpp 內) 所以由 KeyboardInputMapper->process() 然後送到 processKey() function
void QueuedInputListener::notifyKey(const NotifyKeyArgs* args) { mArgsQueue.push(new NotifyKeyArgs(*args)); } void QueuedInputListener::flush() { size_t count = mArgsQueue.size(); for (size_t i = 0; i < count; i++) { NotifyArgs* args = mArgsQueue[i]; args->notify(mInnerListener); delete args; } mArgsQueue.clear(); }
接著會跳到 frameworks/base/service/input/InputListener.cpp
中,剛剛 processKey() 中會把這些 Event 的資訊丟到 QueuedInputListener::notifyKey() 然後 push 到 mArgqueue 之中,每次flush的時候交給 mInnerListener 去處理
void InputDispatcher::notifyKey(const NotifyKeyArgs* args) { ... KeyEvent event; event.initialize(args->deviceId, args->source, args->action, flags, args->keyCode, args->scanCode, metaState, 0, args->downTime, args->eventTime); mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags); ... }
再來剛剛的 mListener 其實就是 InputDispatcher 所以接下來會接到 frameworks/base/service/input/InputDispatcher.cpp
之中的 notifyKey function,並在此第一次打包成 KeyEvent 的格式 (還不是 Java 物件)
然後送到 mPolicy->interceptKeyBeforeQueueing()
void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags){ ... JNIEnv* env = jniEnv(); jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent); if (keyEventObj) { wmActions = env->CallIntMethod(mServiceObj, gServiceClassInfo.interceptKeyBeforeQueueing, keyEventObj, policyFlags, isScreenOn); ... } }
而這個 mPolict 就是 InputManagerService 的 native 物件,位於 frameworks/base/service/jni/com_android_server_input_ImputManagerService.cpp
然後再這裡會透過 JNI 的 function android_view_KeyEvent_fromNative() 把底層的 KeyEvent 轉成 Java 的 KeyEvent 物件
在下來透過 native callback 的方式,把做好的 KeyEvent 物件(Java版) 丟回 Java層的 InputManagerService,位置在 frameworks/base/service/java/com/android/server/input/InputManagerService.java
裡面的 interceptKeyBeforeQueueing() 接著裡面也只做一件事,把這個 Event 繼續 pass 給 mWindowManagerCallbacks 也就是 Android 上層最早接受到 KeyEvent 的 PhoneWindowManager
其位置在frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
所有 Activity 會收到的 KeyEvent,或是系統會前置處理的 KeyEvent (比方說 HOME鍵,一些實體鍵盤的快捷鍵) 都是由這個位置開始的,藉由這次工作上剛好有機會去 trace 這一串 call flow,也順便記錄一下,未來有需要的時候可以參考。