Android系統--事件讀取及分發
1. 簡介
WindowManagerService分發事件是通過它的InputManager來完成的。
在初始化時,各部分狀態如下:
• InputManager.InputReader:正在睡眠等待事件的發生
• InputManager.InputDispatcher:正在等待InputReader從睡眠中醒過來並且喚醒它
• Activity應用程式:正在訊息迴圈中等待InputDispatcher把它喚醒
初始化之後,如果有事件發生,其呼叫流程見下面的內容。
2. 事件分發流程
2.1 Server端
• InputReader
被通知是否有事件可讀
2) EventHub.getEvent (EventHub.cpp)
讀取真正的事件
3) InputReader.process (InputReader.cpp)
4) InputReader.consumeEvent (InputReader.cpp)
5) InputDevice.process (InputReader.cpp)
6) mapper->process(rawEvent) (下面以鍵盤為例)
則真正呼叫: KeyboardInputMapper.process (InputReader.cpp)
7) KeyboardInputMapper.processKey (InputReader.cpp)
• InputDispatcher
8) InputDispatcher.notifyKey (InputDispatcher.cpp)
a) 生成KeyEvent (通過呼叫event.initialize)
b) 生成KeyEntry
c) 呼叫enqueueInboundEventLocked(newEntry),把KeyEntry加入到InputDispatcher類的mInboundQueue佇列中
d) 根據需要喚醒InputDispatccherThread執行緒
9) InputDispatcher.dispatchOnce (InputDispatcher.cpp)
10) InputDispatcher.dispatchOnceInnerLocked (InputDispatcher.cpp)
從mInboundQueue佇列中取出EventEntry
11) InputDispatcher.dispatchKeyLocked (InputDispatcher.cpp)
a) 從當前啟用視窗mFocusedWindowHandle中獲取InputWindowInfo
b) 從InputWindowInfo中獲取inputChannel、frameLeft、frameTop並儲存在mCurrentInputTargets的top InputTarget成員中。後面InputDispatcher就會從mCurrentInputTargets中取出恰當的Activity視窗,然後把鍵盤事件分發給它
c) 呼叫dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false)
12) InputDispatcher.dispatchEventToCurrentInputTargetsLocked (InputDispatcher.cpp)
// InputDispatcher.cpp
void InputDispatcher::dispatchEventToCurrentInputTargetsLocked(nsecs_t currentTime,
EventEntry* eventEntry, bool resumeWithAppendedMotionSample) {
LOG_ASSERT(eventEntry->dispatchInProgress); // should already have been set to true
pokeUserActivityLocked(eventEntry);
for (size_t i = 0; i < mCurrentInputTargets.size(); i++) {
const InputTarget& inputTarget = mCurrentInputTargets.itemAt(i);
ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
if (connectionIndex >= 0) {
// 獲取對應的Connection
sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
prepareDispatchCycleLocked(currentTime, connection, eventEntry, & inputTarget,
resumeWithAppendedMotionSample);
} else {
}
}
}
13) InputDispatcher.prepareDispatchCycleLocked (InputDispatcher.cpp)
14) InputDispatcher.enqueueDispatchEntriesLocked (InputDispatcher.cpp)
(1) InputDispatcher::enqueueDispatchEntryLocked (InputDispatcher.cpp)
a) 生成DispatchEntry
b) 把dispatchEntry加入connection->outboundQueue
(connection->outboundQueue.enqueueAtTail(dispatchEntry))
(2) InputDispatcher::startDispatchCycleLocked(以前佇列空才執行)
15) InputDispatcher.startDispatchCycleLocked (InputDispatcher.cpp)
(1) 從connection->outboundQueue中取出DispatchEntry
(2) 對於EventEntry::TYPE_KEY,呼叫connection->inputPublisher.publishKeyEvent
或 對於EventEntry::TYPE_MOTION,呼叫connection->inputPublisher.publishMotionEvent
(3) InputPublisher.publishKeyEvent (InputTransport.cpp)
它把key event存入ashmem buffer中,即mSharedMessage->key中,
此ashmem buffer在InputDispatcher.registerInputChannel (connection->initialize)被建立。
(4) 傳送dispath訊號(即寫傳送pipe)給Activity應用程式
connection->inputPublisher.sendDispatchSignal-> InputChannel.sendSignal
status_t InputPublisher::sendDispatchSignal() {
mWasDispatched = true;
return mChannel->sendSignal(INPUT_SIGNAL_DISPATCH);
}
至此,已經向Activity應用程式接收pipe中寫入內容,則Activity應用程式的主執行緒就被喚醒了,開始處理此事件,即Activity應用程式上場了!
2.2 Client端 (Activity應用程式)
當應用程式的主執行緒因為這個InputChannel中的讀管道被寫端喚醒時,NativeInputQueue的成員函式handleReceiveCallback就會被回撥,因此,接下來,應用程式的主執行緒就會被喚醒,然後執行NativeInputQueue的成員函式handleReceiveCallback。
• NativeInputQueue
1) NativeInputQueue::handleReceiveCallback (android_view_InputQueue.cpp)
詳細見容見下面的註釋
int NativeInputQueue::handleReceiveCallback(int receiveFd, int events, void* data) {
NativeInputQueue* q = static_cast<NativeInputQueue*>(data);
JNIEnv* env = AndroidRuntime::getJNIEnv();
sp<Connection> connection;
InputEvent* inputEvent;
jobject inputHandlerObjLocal;
jlong finishedToken;
{ // acquire lock
AutoMutex _l(q->mLock);
// 根據receiveFd獲取 connectionIndex
ssize_t connectionIndex = q->mConnectionsByReceiveFd.indexOfKey(receiveFd);
...
// 根據connectionIndex獲取NativeInputQueue.Connection
connection = q->mConnectionsByReceiveFd.valueAt(connectionIndex);
...
// 從管道讀取資料並檢查是否與傳送方寫的一致
status_t status = connection->inputConsumer.receiveDispatchSignal();
...
// 獲取NativeInputQueue.Connection中的InputConsumer物件,它與Server端的InputPublisher對應
// InputConsumer::consume (InputTransport.cpp), 負責把事件讀出來(MotionEvent或KeyEvent)並生成inputEventObj
status = connection->inputConsumer.consume(& connection->inputEventFactory, & inputEvent);
...
connection->messageInProgress = true;
connection->messageSeqNum += 1;
finishedToken = generateFinishedToken(receiveFd, connection->id, connection->messageSeqNum);
// 獲取inputHandlerObjLocal物件
// 在NativeInputQueue.registerInputChannel中Java傳入,並儲存在Connection中
inputHandlerObjLocal = env->NewLocalRef(connection->inputHandlerObjGlobal);
} // release lock
int32_t inputEventType = inputEvent->getType();
jobject inputEventObj;
jmethodID dispatchMethodId;
switch (inputEventType) {
case AINPUT_EVENT_TYPE_KEY:
// 生成inputEventObj
inputEventObj = android_view_KeyEvent_fromNative(env,
static_cast<KeyEvent*>(inputEvent));
// 以指定inputHandlerObjLocal呼叫InputQueue.dispatchKeyEvent來處理此事件
dispatchMethodId = gInputQueueClassInfo.dispatchKeyEvent;
break;
case AINPUT_EVENT_TYPE_MOTION:
inputEventObj = android_view_MotionEvent_obtainAsCopy(env,
static_cast<MotionEvent*>(inputEvent));
dispatchMethodId = gInputQueueClassInfo.dispatchMotionEvent;
break;
default:
assert(false); // InputConsumer should prevent this from ever happening
inputEventObj = NULL;
}
if (! inputEventObj) {
LOGW("channel '%s' ~ Failed to obtain DVM event object.",
connection->getInputChannelName());
env->DeleteLocalRef(inputHandlerObjLocal);
q->finished(env, finishedToken, false, false);
return 1;
}
// 通知Java層的InputQueue來處理這個事件
env->CallStaticVoidMethod(gInputQueueClassInfo.clazz,
dispatchMethodId, inputHandlerObjLocal, inputEventObj,
jlong(finishedToken));
if (env->ExceptionCheck()) {
LOGE("An exception occurred while invoking the input handler for an event.");
LOGE_EX(env);
env->ExceptionClear();
q->finished(env, finishedToken, false, true /*ignoreSpuriousFinish*/);
}
env->DeleteLocalRef(inputEventObj);
env->DeleteLocalRef(inputHandlerObjLocal);
return 1;
}
• NativeInputQueue (下面以處理KeyEvent來講)
2) InputQueue.dispatchKeyEvent (InputQueue.java)
private static void dispatchKeyEvent(InputHandler inputHandler,
KeyEvent event, long finishedToken) {
FinishedCallback finishedCallback = FinishedCallback.obtain(finishedToken);
// inputHandler是呼叫InputQueue.registerInputChannel時傳進來的
// 在ViewRootImpl.setView中呼叫
// InputQueue.registerInputChannel(mInputChannel, mInputHandler,
// Looper.myQueue())
inputHandler.handleKey(event, finishedCallback);
}
mInputHandler的定義如下:
private final InputHandler mInputHandler = new InputHandler() {
public void handleKey(KeyEvent event, InputQueue.FinishedCallback finishedCallback) {
startInputEvent(finishedCallback);
dispatchKey(event, true);
}
public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback) {
startInputEvent(finishedCallback);
dispatchMotion(event, true);
}
};
3) mInputHandler.handleKey (ViewRootImpl.java)
4) ViewRootImpl.dispatchKey (ViewRootImpl.java)
把KeyEvent封裝成Message
5) ViewRootImpl.enqueueInputEvent
把Message轉換為InputEventMessage訊息,然後放於mPendingInputEvents連結串列尾。
ViewRootImpl不直接處理這個事件,而是把它作為一個訊息(DISPATCH_KEY)放到訊息佇列中去處理,這個訊息最後由ViewRootImpl類的deliverKeyEvent成員函式來處理
6) 在handleMessage中呼叫deliverKeyEvent
7) ViewRootImpl.deliverKeyEvent (ViewRootImpl.java)
private void deliverKeyEvent(KeyEvent event, boolean sendDone) {
if (ViewDebug.DEBUG_LATENCY) {
mInputEventDeliverTimeNanos = System.nanoTime();
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onKeyEvent(event, 0);
}
// If there is no view, then the event will not be handled.
if (mView == null || !mAdded) {
finishKeyEvent(event, sendDone, false);
return;
}
// Perform predispatching before the IME.
if (mView.dispatchKeyEventPreIme(event)) {
finishKeyEvent(event, sendDone, true);
return;
}
// InputMethodManager處理完這個鍵盤事件後,再回呼叫這裡的
// mInputMethodCallback物件的finishedEvent成員函式來把鍵盤
// 事件分發給當前啟用的Activity視窗處理。當然,在把這個鍵盤事件
// 分發給InputMethodManager處理之前,ViewRoot也會先把這個鍵盤事
// 件分發給當前啟用的Activity視窗的dispatchKeyEventPreIme成員函式處理。
// Dispatch to the IME before propagating down the view hierarchy.
// The IME will eventually call back into handleFinishedEvent.
if (mLastWasImTarget) {
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null) {
int seq = enqueuePendingEvent(event, sendDone);
imm.dispatchKeyEvent(mView.getContext(), seq, event, mInputMethodCallback);
return;
}
}
// Not dispatching to IME, continue with post IME actions.
deliverKeyEventPostIme(event, sendDone);
}
8) InputMethodCallack.finishedEvent (ViewRootImpl.java)
9) ViewRootImpl.dispatchFinishedEvent (ViewRootImpl.java)
傳送FINISHED_EVENT到佇列
10) ViewRootImpl.dispatchFinishedEvent (ViewRootImpl.java)
如果InputMethodManager沒有處理這個鍵盤事件,那麼ViewRoot就會把
這個鍵盤事件分發給當前啟用的Activity視窗來處理。
11) ViewRootImpl.deliverKeyEventPostIme (ViewRootImpl.java)
12) mView.dispatchKeyEvent(event) (ViewRootImpl.java)
把事件分發給view hierarchy, mView為DecorView
13) DecorView.dispatchKeyEvent (PhoneWindow.java)
public boolean dispatchKeyEvent(KeyEvent event) {
final int keyCode = event.getKeyCode();
final int action = event.getAction();
final boolean isDown = action == KeyEvent.ACTION_DOWN;
if (!isDestroyed()) {
// 返回當前應用程式的啟用的Activity視窗的Window.Callback介面,一般不為NULL
// 因此,這個函式會呼叫Activity類的dispatchKeyEvent來處理這個鍵盤事件
final Callback cb = getCallback();
final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
: super.dispatchKeyEvent(event);
if (handled) {
return true;
}
}
return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event)
: PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event);
}
14) Activity.dispatchKeyEvent (Activity.java)
public boolean dispatchKeyEvent(KeyEvent event) {
onUserInteraction();
Window win = getWindow();
if (win.superDispatchKeyEvent(event)) {
return true;
}
View decor = mDecor;
if (decor == null) decor = win.getDecorView();
return event.dispatch(this, decor != null
? decor.getKeyDispatcherState() : null, this);
}
Activity不是直接處理這個鍵盤事件,而是通過KeyEvent的dispatch轉發一下。
注意,KeyEvent的成中函式dispatch的第一個引數的型別是KeyEvent.Callback,而Activity實現了這個介面,因此,這裡可以傳this引用過去。
15) KeyEvent.dispatch (KeyEvent.java)
根據一個鍵是按下(ACTION_DOWN)、還是鬆開(ACTION_UP) 或者是一個相同的鍵被多次按下和鬆開(ACTION_MULTIPLE)等不同事件型別來分別呼叫(receiver為Activity)Activity的onKeyDown、onKeyLongPres、sonKeyUp和onKeyMultiple函數了。
執行完此函式,然後再一層一層向上返回。