前言
在上一篇文章中,我們學習了輸入事件的處理,輸入事件會交由InputDispatcher進行分發,那麼InputDispatcher是如何進行分發的?這篇文章會給你答案。
1.InputReader的加工型別
在Android輸入系統(二)IMS的啟動過程和輸入事件的處理這篇文章中,我們知道InputReader會對原始輸入事件進行加工,如果事件的型別為按鍵型別的事件,就會呼叫如下一段程式碼。
frameworks/native/services/inputflinger/InputDispatcher.cpp
void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
...
bool needWake;
{
...
} // release lock
if (needWake) {
mLooper->wake();
}
}
複製程式碼
InputDispatcher的notifyKey方法用於喚醒InputDispatcherThread,它的引數NotifyKeyArgs是InputReader對按鍵型別的事件加工後得到的。
frameworks/native/services/inputflinger/InputListener.h
struct NotifyKeyArgs : public NotifyArgs {
nsecs_t eventTime;
int32_t deviceId;
uint32_t source;
uint32_t policyFlags;
int32_t action;
int32_t flags;
int32_t keyCode;
int32_t scanCode;
int32_t metaState;
nsecs_t downTime;
inline NotifyKeyArgs() { }
NotifyKeyArgs(nsecs_t eventTime, int32_t deviceId, uint32_t source, uint32_t policyFlags,
int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode,
int32_t metaState, nsecs_t downTime);
NotifyKeyArgs(const NotifyKeyArgs& other);
virtual ~NotifyKeyArgs() { }
virtual void notify(const sp<InputListenerInterface>& listener) const;
};
複製程式碼
可以看到,NotifyKeyArgs結構體繼承自NotifyArgs結構體,如下圖所示。
NotifyArgs有三個子類,分別是NotifyKeyArgs、NotifyMotionArgs和NotifySwichArgs,這說明InputReader對原始輸入事件加工後,最終會得出三種事件型別,分別是key事件、Motion事件和Swich事件,這些事件會交由InputDispatcher來進行分發,如下圖所示。
2.InputDispatcher的分發過程
不同的事件型別有著不同的分發過程,其中Swich事件的處理是沒有派發過程的,在InputDispatcher的notifySwitch函式中會將Swich事件交由InputDispatcherPolicy來處理。本系列文章一直講解key事件相關,這次換一下,以Motion事件的分發過程來進行舉例,對key事件分發事件有興趣的可以自行去看原始碼,本質上都差不多。
2.1 喚醒InputDispatcherThread
InputDispatcher的notifyMotion函式用來喚醒InputDispatcherThread。
frameworks/native/services/inputflinger/InputDispatcher.cpp
void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
#if DEBUG_INBOUND_EVENT_DETAILS
...
#endif
//檢查Motion事件的引數是否有效
if (!validateMotionEvent(args->action, args->actionButton,
args->pointerCount, args->pointerProperties)) {//1
return;
}
uint32_t policyFlags = args->policyFlags;
policyFlags |= POLICY_FLAG_TRUSTED;
mPolicy->interceptMotionBeforeQueueing(args->eventTime, /*byref*/ policyFlags);
bool needWake;
{ // acquire lock
mLock.lock();
//Motion事件是否需要交由InputFilter過濾
if (shouldSendMotionToInputFilterLocked(args)) {//2
mLock.unlock();
MotionEvent event;
//初始化MotionEvent,將NotifyMotionArgs中的引數資訊賦值給MotionEvent中的引數
event.initialize(args->deviceId, args->source, args->action, args->actionButton,
args->flags, args->edgeFlags, args->metaState, args->buttonState,
0, 0, args->xPrecision, args->yPrecision,
args->downTime, args->eventTime,
args->pointerCount, args->pointerProperties, args->pointerCoords);
//表示已經過濾了
policyFlags |= POLICY_FLAG_FILTERED;
//開始過濾,如果返回值為false,就會直接return,這次事件不再進行分發
if (!mPolicy->filterInputEvent(&event, policyFlags)) {//3
return; // event was consumed by the filter
}
mLock.lock();
}
/**
* 4
*/
MotionEntry* newEntry = new MotionEntry(args->eventTime,
args->deviceId, args->source, policyFlags,
args->action, args->actionButton, args->flags,
args->metaState, args->buttonState,
args->edgeFlags, args->xPrecision, args->yPrecision, args->downTime,
args->displayId,
args->pointerCount, args->pointerProperties, args->pointerCoords, 0, 0);
needWake = enqueueInboundEventLocked(newEntry);//5
mLock.unlock();
} // release lock
if (needWake) {
mLooper->wake();//6
}
}
複製程式碼
註釋1處用於檢查Motion事件的引數是否有效,其內部會檢查觸控點的數量pointerCount是否在合理範圍內(小於1或者大於16都是不合理的),以及觸控點的ID是否在合理範圍內(小於0或者大於31都是不合理的)。
註釋2處如果Motion事件需要交由InputFilter過濾,就會初始化MotionEvent,其作用就是用NotifyMotionArgs中的事件引數資訊構造一個MotionEvent,接著MotionEven會交給註釋3處的方法進行過濾,如果返回值為false,這次Motion事件就會被忽略掉。
註釋4處,用NotifyMotionArgs中的事件引數資訊構造一個MotionEntry物件。註釋5處將MotionEntry傳入到enqueueInboundEventLocked函式中,其內部會將MotionEntry新增到InputDispatcher的mInboundQueue佇列的末尾,並返回一個值needWake,代表InputDispatcherThread是否需要喚醒,如果需要喚醒就呼叫註釋6處的程式碼來喚醒InputDispatcherThread。
2.2 InputDispatcher進行分發
InputDispatcherThread被喚醒後,會執行InputDispatcherThread的threadLoop函式:
frameworks/native/services/inputflinger/InputDispatcher.cpp
bool InputDispatcherThread::threadLoop() {
mDispatcher->dispatchOnce();
return true;
}
複製程式碼
threadLoop函式中只呼叫了InputDispatcher的dispatchOnce函式:
frameworks/native/services/inputflinger/InputDispatcher.cpp
void InputDispatcher::dispatchOnce() {
nsecs_t nextWakeupTime = LONG_LONG_MAX;
{ // acquire lock
AutoMutex _l(mLock);
mDispatcherIsAliveCondition.broadcast();
if (!haveCommandsLocked()) {//1
dispatchOnceInnerLocked(&nextWakeupTime);//2
}
if (runCommandsLockedInterruptible()) {
nextWakeupTime = LONG_LONG_MIN;
}
} // release lock
nsecs_t currentTime = now();//3
int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);//4
mLooper->pollOnce(timeoutMillis);
}
複製程式碼
註釋1處用於檢查InputDispatcher的快取佇列中是否有等待處理的命令,如果沒有就會執行註釋2處的dispatchOnceInnerLocked函式,用來將輸入事件分發給合適的。註釋3處獲取當前的時間,結合註釋4處,得出InputDispatcherThread需要睡眠的時間為timeoutMillis。最後呼叫Looper的pollOnce函式使InputDispatcherThread進入睡眠狀態,並將它的最長的睡眠的時間設定為timeoutMillis。當有輸入事件產生時,InputReader就會將睡眠狀態的InputDispatcherThread
喚醒,InputDispatcher會重新開始分發輸入事件。檢視註釋2處的dispatchOnceInnerLocked函式是如何進行事件分發的。
frameworks/native/services/inputflinger/InputDispatcher.cpp
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
...
// 如果InputDispatcher被凍結,則不進行派發操作
if (mDispatchFrozen) {
#if DEBUG_FOCUS
ALOGD("Dispatch frozen. Waiting some more.");
#endif
return;
}
//如果isAppSwitchDue為true,說明沒有及時響應HOME鍵等操作
bool isAppSwitchDue = mAppSwitchDueTime <= currentTime;//1
if (mAppSwitchDueTime < *nextWakeupTime) {//2
*nextWakeupTime = mAppSwitchDueTime;
}
//如果還沒有待分發的事件,去mInboundQueue中取出一個事件
if (! mPendingEvent) {
//如果mInboundQueue為空,並且沒有待分發的事件,就return
if (mInboundQueue.isEmpty()) {
...
if (!mPendingEvent) {
return;
}
} else {
//如果mInboundQueue不為空,取佇列頭部的EventEntry賦值給mPendingEvent
mPendingEvent = mInboundQueue.dequeueAtHead();
traceInboundQueueLengthLocked();
}
if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) {
pokeUserActivityLocked(mPendingEvent);
}
resetANRTimeoutsLocked();
}
ALOG_ASSERT(mPendingEvent != NULL);
bool done = false;
DropReason dropReason = DROP_REASON_NOT_DROPPED;//3
...
switch (mPendingEvent->type) {//4
...
case EventEntry::TYPE_MOTION: {
MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);
//如果沒有及時響應視窗切換操作
if (dropReason == DROP_REASON_NOT_DROPPED && isAppSwitchDue) {
dropReason = DROP_REASON_APP_SWITCH;
}
//事件過期
if (dropReason == DROP_REASON_NOT_DROPPED
&& isStaleEventLocked(currentTime, typedEntry)) {
dropReason = DROP_REASON_STALE;
}
//阻礙其他視窗獲取事件
if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {
dropReason = DROP_REASON_BLOCKED;
}
done = dispatchMotionLocked(currentTime, typedEntry,
&dropReason, nextWakeupTime);//5
break;
}
default:
ALOG_ASSERT(false);
break;
}
if (done) {
if (dropReason != DROP_REASON_NOT_DROPPED) {
dropInboundEventLocked(mPendingEvent, dropReason);
}
mLastDropReason = dropReason;
//釋放本次事件處理的物件
releasePendingEventLocked();//6
//使得InputDispatcher能夠快速處理下一個分發事件
*nextWakeupTime = LONG_LONG_MIN;//7
}
複製程式碼
InputDispatcher的dispatchOnceInnerLocked函式的程式碼比較長,這裡擷取了和Motion事件的分發相關的主要原始碼。主要做了以下幾件事。
- InputDispatcher的凍結處理
如果當前InputDispatcher被凍結,則不進行派發操作,InputDispatcher有三種狀態,分別是正常狀態、凍結狀態和禁用狀態,可以通過InputDispatcher的setInputDispatchMode函式來設定。 - 視窗切換操作處理
註釋1處的mAppSwitchDueTime ,代表了App最近發生視窗切換操作時(比如按下Home鍵、結束通話電話),該操作事件最遲的分發時間。如果這個時候,mAppSwitchDueTime小於等於當前系統時間,說明沒有及時響應視窗切換操作,則isAppSwitchDue的值設定為true。
註釋2處,如果mAppSwitchDueTime小於nextWakeupTime(下一次InputDispatcherThread醒來的時間),就將mAppSwitchDueTime賦值給nextWakeupTime,這樣當InputDispatcher處理完分發事件後,會第一時間處理視窗切換操作。 - 取出事件
如果沒有待分發的事件,就從mInboundQueue中取出一個事件,如果mInboundQueue為空,並且沒有待分發的事件,就return,如果mInboundQueue不為空,取佇列頭部的EventEntry賦值給mPendingEvent,mPendingEvent的型別為EventEntry物件指標。 - 事件丟棄
註釋3處的dropReason代表了事件丟棄的原因,它的預設值為DROP_REASON_NOT_DROPPED,代表事件不被丟棄。
註釋4處根據mPendingEvent的type做區分處理,這裡主要擷取了對Motion型別的處理。經過過濾,會呼叫註釋5處的dispatchMotionLocked函式為這個事件尋找合適的視窗。 - 後續處理
如果註釋5處的事件分發成功,則會在註釋6處呼叫releasePendingEventLocked函式,其內部會將mPendingEvent的值設定為Null,並將mPendingEvent指向的物件記憶體釋放掉。註釋7處將nextWakeupTime的值設定為LONG_LONG_MIN,這是為了讓InputDispatcher能夠快速處理下一個分發事件。
後記
本文講解了InputReader的加工型別和InputDispatcher的分發過程,由於文章篇幅的原因,InputDispatcher的分發過程還有一部分沒有講解,這一部分就是事件分發到目標視窗的過程,會在本系列的下一篇文章進行講解。
分享大前端、Java、跨平臺等技術,關注職業發展和行業動態。