Android輸入系統(三)InputReader的加工型別和InputDispatcher的分發過程

劉望舒發表於2019-02-26

關聯絡列
解析WMS系列
深入理解JNI系列
輸入系統系列

前言

在上一篇文章中,我們學習了輸入事件的處理,輸入事件會交由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結構體,如下圖所示。

Android輸入系統(三)InputReader的加工型別和InputDispatcher的分發過程

NotifyArgs有三個子類,分別是NotifyKeyArgs、NotifyMotionArgs和NotifySwichArgs,這說明InputReader對原始輸入事件加工後,最終會得出三種事件型別,分別是key事件、Motion事件和Swich事件,這些事件會交由InputDispatcher來進行分發,如下圖所示。

Android輸入系統(三)InputReader的加工型別和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事件的分發相關的主要原始碼。主要做了以下幾件事。

  1. InputDispatcher的凍結處理
    如果當前InputDispatcher被凍結,則不進行派發操作,InputDispatcher有三種狀態,分別是正常狀態、凍結狀態和禁用狀態,可以通過InputDispatcher的setInputDispatchMode函式來設定。
  2. 視窗切換操作處理
    註釋1處的mAppSwitchDueTime ,代表了App最近發生視窗切換操作時(比如按下Home鍵、結束通話電話),該操作事件最遲的分發時間。如果這個時候,mAppSwitchDueTime小於等於當前系統時間,說明沒有及時響應視窗切換操作,則isAppSwitchDue的值設定為true。
    註釋2處,如果mAppSwitchDueTime小於nextWakeupTime(下一次InputDispatcherThread醒來的時間),就將mAppSwitchDueTime賦值給nextWakeupTime,這樣當InputDispatcher處理完分發事件後,會第一時間處理視窗切換操作。
  3. 取出事件
    如果沒有待分發的事件,就從mInboundQueue中取出一個事件,如果mInboundQueue為空,並且沒有待分發的事件,就return,如果mInboundQueue不為空,取佇列頭部的EventEntry賦值給mPendingEvent,mPendingEvent的型別為EventEntry物件指標。
  4. 事件丟棄
    註釋3處的dropReason代表了事件丟棄的原因,它的預設值為DROP_REASON_NOT_DROPPED,代表事件不被丟棄。
    註釋4處根據mPendingEvent的type做區分處理,這裡主要擷取了對Motion型別的處理。經過過濾,會呼叫註釋5處的dispatchMotionLocked函式為這個事件尋找合適的視窗。
  5. 後續處理
    如果註釋5處的事件分發成功,則會在註釋6處呼叫releasePendingEventLocked函式,其內部會將mPendingEvent的值設定為Null,並將mPendingEvent指向的物件記憶體釋放掉。註釋7處將nextWakeupTime的值設定為LONG_LONG_MIN,這是為了讓InputDispatcher能夠快速處理下一個分發事件。

後記

本文講解了InputReader的加工型別和InputDispatcher的分發過程,由於文章篇幅的原因,InputDispatcher的分發過程還有一部分沒有講解,這一部分就是事件分發到目標視窗的過程,會在本系列的下一篇文章進行講解。


分享大前端、Java、跨平臺等技術,關注職業發展和行業動態。

Android輸入系統(三)InputReader的加工型別和InputDispatcher的分發過程

相關文章