最近追蹤了一下 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 上也蠻好用的。
完整的 call flow 有興趣的可以點開
切入正題,目前看到 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,也順便記錄一下,未來有需要的時候可以參考。