1. 程式人生 > >你需要掌握的事件分發高階知識

你需要掌握的事件分發高階知識

戳藍字“CSDN雲端計算”關注我們哦!

系列前作

1. Android輸入系統的事件傳遞流程和IMS的誕生

2. 只瞭解View的事件分發是不夠的,來看下輸入系統對事件的處理

1.InputReader的加工型別

只瞭解View的事件分發是不夠的,來看下輸入系統對事件的處理這篇文章中,我們知道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結構體,如下圖所示。

640?wx_fmt=png

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

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,
                    00, 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, 00);
        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(下一次InputDispatcher醒來的時間),就將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的分發過程還有一部分沒有講解,這一部分就是事件分發到目標視窗的過程,會在本系列的下一篇文章進行講解。

文章轉自公眾號:劉舒望

— — — END — — —


1.微信群:

新增小編微信:color_ld,備註“進群+姓名+公司職位”即可,加入【雲端計算學習交流群】,和志同道合的朋友們共同打卡學習!


2.徵稿:

投稿郵箱:[email protected];微訊號:color_ld。請備註投稿+姓名+公司職位。



推薦閱讀


640?wx_fmt=png

↓點選“閱讀原文”,開啟APP 閱讀更順暢