欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android開發(fā)Input系統(tǒng)觸摸事件分發(fā)

 更新時間:2023年03月02日 11:08:36   作者:大胃粥  
這篇文章主要為大家介紹了Android開發(fā)Input系統(tǒng)觸摸事件分發(fā)示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

引言

Input系統(tǒng): InputReader 處理觸摸事件 分析了 InputReader 對觸摸事件的處理流程,最終的結(jié)果是把觸摸事件包裝成 NotifyMotionArgs,然后分發(fā)給下一環(huán)。根據(jù) Input系統(tǒng): InputManagerService的創(chuàng)建與啟動 可知,下一環(huán)是 InputClassifier。然而系統(tǒng)目前并不支持 InputClassifier 的功能,因此事件會被直接發(fā)送到 InputDispatcher。

Input系統(tǒng): 按鍵事件分發(fā) 分析了按鍵事件的分發(fā)流程,雖然分析的目標(biāo)是按鍵事件,但是也從整體上,描繪了事件分發(fā)的框架。而本文分析觸摸事件的分發(fā)流程,也會用到這個框架,因此重復(fù)內(nèi)容不再贅述。

1. InputDispatcher 收到觸摸事件

void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
    if (!validateMotionEvent(args->action, args->actionButton, args->pointerCount,
                             args->pointerProperties)) {
        return;
    }
    uint32_t policyFlags = args->policyFlags;
    // 來自InputReader/InputClassifier的 motion 事件,都是受信任的
    policyFlags |= POLICY_FLAG_TRUSTED;
    android::base::Timer t;
    // 1. 對觸摸事件執(zhí)行截斷策略
    // 觸摸事件入隊前,查詢截斷策略,查詢的結(jié)果保存到參數(shù) policyFlags
    mPolicy->interceptMotionBeforeQueueing(args->displayId, args->eventTime, /*byref*/ policyFlags);
    if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
        ALOGW("Excessive delay in interceptMotionBeforeQueueing; took %s ms",
              std::to_string(t.duration().count()).c_str());
    }
    bool needWake;
    { // acquire lock
        mLock.lock();
        if (shouldSendMotionToInputFilterLocked(args)) {
            // ...
        }
        // 包裝成 MotionEntry
        // Just enqueue a new motion event.
        std::unique_ptr<MotionEntry> newEntry =
                std::make_unique<MotionEntry>(args->id, args->eventTime, args->deviceId,
                                              args->source, args->displayId, policyFlags,
                                              args->action, args->actionButton, args->flags,
                                              args->metaState, args->buttonState,
                                              args->classification, args->edgeFlags,
                                              args->xPrecision, args->yPrecision,
                                              args->xCursorPosition, args->yCursorPosition,
                                              args->downTime, args->pointerCount,
                                              args->pointerProperties, args->pointerCoords, 0, 0);
        // 2. 把觸摸事件加入收件箱
        needWake = enqueueInboundEventLocked(std::move(newEntry));
        mLock.unlock();
    } // release lock
    // 3. 如果有必要,喚醒線程處理觸摸事件
    if (needWake) {
        mLooper->wake();
    }
}

InputDispatcher 收到觸摸事件后的處理流程,與收到按鍵事件的處理流程非常相似

  • 對觸摸事件進行截斷策略查詢。參考【1.1 截斷策略查詢】
  • 把觸摸事件加入 InputDispatcher 收件箱,然后喚醒線程處理觸摸事件。

1.1 截斷策略查詢

void NativeInputManager::interceptMotionBeforeQueueing(const int32_t displayId, nsecs_t when,
        uint32_t&amp; policyFlags) {
    bool interactive = mInteractive.load();
    if (interactive) {
        policyFlags |= POLICY_FLAG_INTERACTIVE;
    }
    // 受信任,并且是非注入的事件
    if ((policyFlags &amp; POLICY_FLAG_TRUSTED) &amp;&amp; !(policyFlags &amp; POLICY_FLAG_INJECTED)) {
        if (policyFlags &amp; POLICY_FLAG_INTERACTIVE) {
            // 設(shè)備處于交互狀態(tài)下,受信任且非注入的事件,直接發(fā)送給用戶,而不經(jīng)過截斷策略處理
            policyFlags |= POLICY_FLAG_PASS_TO_USER;
        } else {
            // 只有設(shè)備處于非交互狀態(tài),觸摸事件才需要執(zhí)行截斷策略
            JNIEnv* env = jniEnv();
            jint wmActions = env-&gt;CallIntMethod(mServiceObj,
                        gServiceClassInfo.interceptMotionBeforeQueueingNonInteractive,
                        displayId, when, policyFlags);
            if (checkAndClearExceptionFromCallback(env,
                    "interceptMotionBeforeQueueingNonInteractive")) {
                wmActions = 0;
            }
            handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
        }
    } else { // 注入事件,或者不受信任事件
        // 只有在交互狀態(tài)下,才傳遞給用戶
        // 注意,這里還有另外一層意思: 非交互狀態(tài)下,不發(fā)送給用戶
        if (interactive) {
            policyFlags |= POLICY_FLAG_PASS_TO_USER;
        }
    }
}
void NativeInputManager::handleInterceptActions(jint wmActions, nsecs_t when,
        uint32_t&amp; policyFlags) {
    if (wmActions &amp; WM_ACTION_PASS_TO_USER) {
        policyFlags |= POLICY_FLAG_PASS_TO_USER;
    }
}

一個觸摸事件,必須滿足下面三種情況,才執(zhí)行截斷策略

  • 觸摸事件是受信任的。來自輸入設(shè)備的觸摸事件都是受信任的。
  • 觸摸事件是非注入的。monkey 的原理就是注入觸摸事件,因此它的事件是不需要經(jīng)過截斷策略處理的。
  • 設(shè)備處于非交互狀態(tài)。一般來說,非交互狀態(tài)指的就是顯示屏處于滅屏狀態(tài)。

另外還需要關(guān)注的是,事件在什么時候是不需要經(jīng)過截斷策略,有兩種情況

  • 對于受信任且非注入的觸摸事件,如果設(shè)備處于交互狀態(tài),直接發(fā)送給用戶。 也就是說,如果顯示屏處于亮屏狀態(tài),輸入設(shè)備產(chǎn)生的觸摸事件一定會發(fā)送給窗口。
  • 對于不受信任,或者注入的觸摸事件,如果設(shè)備處于交互狀態(tài),也是直接發(fā)送給用戶。也就是說,如果顯示屏處于亮屏狀態(tài),monkey 注入的觸摸事件,也是直接發(fā)送給窗口的。

最后還要注意一件事,如果一個觸摸事件是不受信任的事件,或者是注入事件,當(dāng)設(shè)備處于非交互狀態(tài)下(通常指滅屏),那么它不經(jīng)過截斷策略,也不會發(fā)送給用戶,也就是會被丟棄。

在實際工作中處理的觸摸事件,通常都是來自輸入設(shè)備,它肯定是受信任的,而且非注入的,因此它只有在設(shè)備處于非交互狀態(tài)下(一般指滅屏)下,非會執(zhí)行截斷策略,而如果設(shè)備處于交互狀態(tài)(通常指亮屏),會被直接分發(fā)給窗口。

現(xiàn)在來看下截斷策略的具體實現(xiàn)

// PhoneWindowManager.java
    public int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos,
            int policyFlags) {
        // 1. 如果策略要求喚醒屏幕,那么截斷這個觸摸事件
        // 一般來說,喚醒屏幕的策略取決于設(shè)備的配置文件
        if ((policyFlags &amp; FLAG_WAKE) != 0) {
            if (wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromMotion,
                    PowerManager.WAKE_REASON_WAKE_MOTION, "android.policy:MOTION")) {
                // 返回 0,表示截斷觸摸事件
                return 0;
            }
        }
        // 2. 判斷非交互狀態(tài)下,是否截斷事件
        if (shouldDispatchInputWhenNonInteractive(displayId, KEYCODE_UNKNOWN)) {
            // 返回這個值,表示不截斷事件,也就是事件分發(fā)給用戶
            return ACTION_PASS_TO_USER;
        }
        // 忽略 theater mode
        if (isTheaterModeEnabled() &amp;&amp; (policyFlags &amp; FLAG_WAKE) != 0) {
            wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromMotionWhenNotDreaming,
                    PowerManager.WAKE_REASON_WAKE_MOTION, "android.policy:MOTION");
        }
        // 3. 默認截斷觸摸事件
        // 返回0,表示截斷事件
        return 0;
    }
    private boolean shouldDispatchInputWhenNonInteractive(int displayId, int keyCode) {
        // Apply the default display policy to unknown displays as well.
        final boolean isDefaultDisplay = displayId == DEFAULT_DISPLAY
                || displayId == INVALID_DISPLAY;
        final Display display = isDefaultDisplay
                ? mDefaultDisplay
                : mDisplayManager.getDisplay(displayId);
        final boolean displayOff = (display == null
                || display.getState() == STATE_OFF);
        if (displayOff &amp;&amp; !mHasFeatureWatch) {
            return false;
        }
        // displayOff 表示屏幕處于 off 狀態(tài),但是非 off 狀態(tài),并不表示一定是亮屏狀態(tài)
        // 對于 doze 狀態(tài),屏幕處于 on 狀態(tài),但是屏幕可能仍然是黑的
        // 因此,只要屏幕處于 on 狀態(tài),并且顯示了鎖屏,觸摸事件不會截斷
        if (isKeyguardShowingAndNotOccluded() &amp;&amp; !displayOff) {
            return true;
        }
        // 對于觸摸事件,keyCode 的值為 KEYCODE_UNKNOWN
        if (mHasFeatureWatch &amp;&amp; (keyCode == KeyEvent.KEYCODE_BACK
                || keyCode == KeyEvent.KEYCODE_STEM_PRIMARY
                || keyCode == KeyEvent.KEYCODE_STEM_1
                || keyCode == KeyEvent.KEYCODE_STEM_2
                || keyCode == KeyEvent.KEYCODE_STEM_3)) {
            return false;
        }
        // 對于默認屏幕,如果設(shè)備處于夢境狀態(tài),那么觸摸事件不截斷
        // 因為 doze 組件需要接收觸摸事件,可能會喚醒屏幕
        if (isDefaultDisplay) {
            IDreamManager dreamManager = getDreamManager();
            try {
                if (dreamManager != null &amp;&amp; dreamManager.isDreaming()) {
                    return true;
                }
            } catch (RemoteException e) {
                Slog.e(TAG, "RemoteException when checking if dreaming", e);
            }
        }
        // Otherwise, consume events since the user can't see what is being
        // interacted with.
        return false;
    }    

截斷策略是否截斷觸摸事件,取決于策略的返回值,有兩種情況

  • 返回 0,表示截斷觸摸事件。
  • 返回 ACTION_PASS_TO_USER ,表示不截斷觸摸事件,也就是把觸摸事件分發(fā)給用戶/窗口。

下面列舉觸摸事件截斷與否的情況,但是要注意一個前提,設(shè)備處于非交互狀態(tài)(一般就是指滅屏狀態(tài))

  • 事件會被傳遞給用戶,也就是不截斷,情況如下
    • 有鎖屏,并且顯示屏處于非 off 狀態(tài)。注意,非 off 狀態(tài),并不是表示屏幕處于 on(亮屏) 狀態(tài),也可能是 doze 狀態(tài)(屏幕處于低電量狀態(tài)),doze 狀態(tài)屏幕也是黑的。
    • 夢境狀態(tài)。因為夢境狀態(tài)下會運行 doze 組件。
  • 事件被截斷,情況如下
    • 策略標(biāo)志位包含 FLAG_WAKE ,它會導(dǎo)致屏幕被喚醒,因此需要截斷觸摸事件。FLAG_WAKE 一般來自于輸入設(shè)備的配置文件。
    • 沒有鎖屏,沒有夢境,也沒有 FLAG_WAKE,默認就會截斷。

從上面的分析可以總結(jié)出了兩條結(jié)論

  • 如果系統(tǒng)有組件在運行,例如,鎖屏、doze組件,那么觸摸事件需要分發(fā)到這些組件,因此不會被截斷。
  • 如果沒有組件運行,觸摸事件都會被截斷。觸摸事件由于需要喚醒屏幕,而導(dǎo)致被截斷,只是其中一個特例。

2. InputDispatcher 分發(fā)觸摸事件

Input系統(tǒng): InputManagerService的創(chuàng)建與啟動 可知,InputDispatcher 通過線程循環(huán)來處理收件箱中的事件,而且一次循環(huán)只能處理一個事件

void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LONG_LONG_MAX;
    { // acquire lock
        std::scoped_lock _l(mLock);
        mDispatcherIsAlive.notify_all();
        if (!haveCommandsLocked()) {
            // 1. 分發(fā)一個觸摸事件
            dispatchOnceInnerLocked(&amp;nextWakeupTime);
        }
        // 觸摸事件的分發(fā)過程不會產(chǎn)生命令
        if (runCommandsLockedInterruptible()) {
            nextWakeupTime = LONG_LONG_MIN;
        }
        // 2. 計算線程下次喚醒的時間點,以便處理 anr
        const nsecs_t nextAnrCheck = processAnrsLocked();
        nextWakeupTime = std::min(nextWakeupTime, nextAnrCheck);
        if (nextWakeupTime == LONG_LONG_MAX) {
            mDispatcherEnteredIdle.notify_all();
        }
    } // release lock
    // 3. 線程休眠指定的時長
    nsecs_t currentTime = now();
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
    mLooper-&gt;pollOnce(timeoutMillis);
}

一次線程循環(huán)處理觸摸事件的過程如下

  • 分發(fā)一個觸摸事件。
  • 當(dāng)事件分發(fā)給窗口后,會計算一個窗口反饋的超時時間,利用這個時間,計算線程下次喚醒的時間點。
  • 利用上一步計算出的線程喚醒的時間點,計算出線程最終需要休眠多長時間。當(dāng)線程被喚醒后,會檢查接收觸摸時間的窗口,是否反饋超時,如果超時,會引發(fā) ANR。

現(xiàn)在來看看如何分發(fā)一個觸摸事件

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    nsecs_t currentTime = now();
    if (!mDispatchEnabled) {
        resetKeyRepeatLocked();
    }
    if (mDispatchFrozen) {
        return;
    }
    // 這里是優(yōu)化 app 切換的延遲
    // mAppSwitchDueTime 是 app 切換的超時時間,如果小于當(dāng)前時間,那么表明app切換超時了
    // 如果app切換超時,那么在app切換按鍵事件之前的未處理的事件,都將會被丟棄
    bool isAppSwitchDue = mAppSwitchDueTime <= currentTime;
    if (mAppSwitchDueTime < *nextWakeupTime) {
        *nextWakeupTime = mAppSwitchDueTime;
    }
    // mPendingEvent 表示正在處理的事件
    if (!mPendingEvent) {
        if (mInboundQueue.empty()) {
            // ...
        } else {
            // 1. 從收件箱隊列中取出事件
            mPendingEvent = mInboundQueue.front();
            mInboundQueue.pop_front();
            traceInboundQueueLengthLocked();
        }
        // 如果這個事件需要傳遞給用戶,那么需要同上層的 PowerManagerService,此時有用戶行為,這個作用就是延長亮屏的時間
        if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) {
            pokeUserActivityLocked(*mPendingEvent);
        }
    }
    ALOG_ASSERT(mPendingEvent != nullptr);
    bool done = false;
    // 檢測丟棄事件的原因
    DropReason dropReason = DropReason::NOT_DROPPED;
    if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) {
        // 被截斷策略截斷
        dropReason = DropReason::POLICY;
    } else if (!mDispatchEnabled) {
        // 一般是由于系統(tǒng)正在系統(tǒng)或者正在關(guān)閉
        dropReason = DropReason::DISABLED;
    }
    if (mNextUnblockedEvent == mPendingEvent) {
        mNextUnblockedEvent = nullptr;
    }
    switch (mPendingEvent->type) {
        // ....
        case EventEntry::Type::MOTION: {
            std::shared_ptr<MotionEntry> motionEntry =
                    std::static_pointer_cast<MotionEntry>(mPendingEvent);
            if (dropReason == DropReason::NOT_DROPPED && isAppSwitchDue) {
                // app 切換超時,導(dǎo)致觸摸事件被丟棄
                dropReason = DropReason::APP_SWITCH;
            }
            if (dropReason == DropReason::NOT_DROPPED && isStaleEvent(currentTime, *motionEntry)) {
                // 10s 之前的事件,已經(jīng)過期
                dropReason = DropReason::STALE;
            }
            // 這里是優(yōu)化應(yīng)用無響應(yīng)的一個措施,會丟棄mNextUnblockedEvent之前的所有觸摸事件
            if (dropReason == DropReason::NOT_DROPPED && mNextUnblockedEvent) {
                dropReason = DropReason::BLOCKED;
            }
            // 2. 分發(fā)觸摸事件
            done = dispatchMotionLocked(currentTime, motionEntry, &dropReason, nextWakeupTime);
            break;
        }
        // ...
    }
    // 3. 如果事件被處理,重置一些狀態(tài),例如 mPendingEvent
    // 返回 true,就表示已經(jīng)處理了事件
    // 事件被丟棄,或者發(fā)送完畢,都會返回 true
    // 返回 false,表示暫時不知道如何處理事件,因此線程會休眠
    // 然后,線程再次被喚醒時,再來處理這個事件
    if (done) {
        if (dropReason != DropReason::NOT_DROPPED) {
            dropInboundEventLocked(*mPendingEvent, dropReason);
        }
        mLastDropReason = dropReason;
        // 重置 mPendingEvent
        releasePendingEventLocked();
        // 立即喚醒,處理下一個事件
        *nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately
    }
}

Input系統(tǒng): 按鍵事件分發(fā) 已經(jīng)分析過 InputDispatcher 的線程循環(huán)。而對于觸摸事件,是通過 InputDispatcher::dispatchMotionLocked() 進行分發(fā)

bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr<MotionEntry> entry,
                                           DropReason* dropReason, nsecs_t* nextWakeupTime) {
    if (!entry->dispatchInProgress) {
        entry->dispatchInProgress = true;
    }
    // 1. 觸摸事件有原因需要丟棄,那么不走后面的分發(fā)流程
    if (*dropReason != DropReason::NOT_DROPPED) {
        setInjectionResult(*entry,
                           *dropReason == DropReason::POLICY ? InputEventInjectionResult::SUCCEEDED
                                                             : InputEventInjectionResult::FAILED);
        return true;
    }
    bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER;
    std::vector<InputTarget> inputTargets;
    bool conflictingPointerActions = false;
    InputEventInjectionResult injectionResult;
    if (isPointerEvent) {
        // 尋找觸摸的窗口,窗口保存到 inputTargets
        // 2. 為觸摸事件,尋找觸摸的窗口
        // 觸摸的窗口保存到 inputTargets 中
        injectionResult =
                findTouchedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime,
                                               &conflictingPointerActions);
    } else {
        // ...
    }
    if (injectionResult == InputEventInjectionResult::PENDING) {
        // 返回 false,表示暫時不知道如何處理這個事件,這會導(dǎo)致線程休眠
        // 等線程下次被喚醒時,再來處理這個事件
        return false;
    }
    // 走到這里,表示觸摸事件已經(jīng)被處理,因此保存處理的結(jié)果
    // 只要返回的不是 InputEventInjectionResult::PENDING
    // 都表示事件被處理,無論是權(quán)限拒絕還是失敗,或是成功
    setInjectionResult(*entry, injectionResult);
    if (injectionResult == InputEventInjectionResult::PERMISSION_DENIED) {
        ALOGW("Permission denied, dropping the motion (isPointer=%s)", toString(isPointerEvent));
        return true;
    }
    if (injectionResult != InputEventInjectionResult::SUCCEEDED) {
        CancelationOptions::Mode mode(isPointerEvent
                                              ? CancelationOptions::CANCEL_POINTER_EVENTS
                                              : CancelationOptions::CANCEL_NON_POINTER_EVENTS);
        CancelationOptions options(mode, "input event injection failed");
        synthesizeCancelationEventsForMonitorsLocked(options);
        return true;
    }
    // 走到這里,表示觸摸事件已經(jīng)成功找到觸摸的窗口
    // Add monitor channels from event's or focused display.
    // 3. 觸摸事件找到了觸摸窗口,在分發(fā)給窗口前,保存 global monitor 到 inputTargets 中
    // 開發(fā)者選項中的 Show taps 和 Pointer location,利用的 global monitor
    addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry));
    if (isPointerEvent) {
        // ... 省略 portal window 處理的代碼
    }
    if (conflictingPointerActions) {
        // ...
    }
    // 4. 分發(fā)事件給 inputTargets 中的所有窗口
    dispatchEventLocked(currentTime, entry, inputTargets);
    return true;
}

一個觸摸事件的分發(fā)過程,可以大致總結(jié)為以下幾個過程

  • 如果有原因表明觸摸事件需要被丟棄,那么觸摸事件不會走后面的分發(fā)流程,即被丟棄。
  • 通常觸摸事件是發(fā)送給窗口的,因此需要為觸摸事件尋找觸摸窗口。窗口最終被保存到 inputTargets 中。參考【2.1 尋找觸摸的窗口】
  • inputTargets 保存觸摸窗口后,還要保存 global monitor 窗口。例如開發(fā)者選項中的 Show taps 和 Pointer location,就是利用這個窗口實現(xiàn)的。
  • 啟動分發(fā)循環(huán),把觸摸事件分發(fā)給 inputTargets 保存的窗口。 由于 Input系統(tǒng): 按鍵事件分發(fā) 已經(jīng)分發(fā)過這個過程,本文不再分析。

2.1 尋找觸摸的窗口

InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked(
        nsecs_t currentTime, const MotionEntry& entry, std::vector<InputTarget>& inputTargets,
        nsecs_t* nextWakeupTime, bool* outConflictingPointerActions) {
    // ...
    // 6. 對于非 DOWN 事件,獲取已經(jīng) DOWN 事件保存的 TouchState
    // TouchState 保存了接收 DOWN 事件的窗口
    const TouchState* oldState = nullptr;
    TouchState tempTouchState;
    std::unordered_map<int32_t, TouchState>::iterator oldStateIt =
            mTouchStatesByDisplay.find(displayId);
    if (oldStateIt != mTouchStatesByDisplay.end()) {
        oldState = &(oldStateIt->second);
        tempTouchState.copyFrom(*oldState);
    }
    // ...
    // 第一個條件 newGesture 表示第一個手指按下
    // 后面一個條件,表示當(dāng)前窗口支持 split motion,并且此時有另外一個手指按下
    if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
        /* Case 1: New splittable pointer going down, or need target for hover or scroll. */
        // 觸摸點的獲取 x, y 坐標(biāo)
        int32_t x;
        int32_t y;
        int32_t pointerIndex = getMotionEventActionPointerIndex(action);
        if (isFromMouse) {
            // ...
        } else {
            x = int32_t(entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_X));
            y = int32_t(entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_Y));
        }
        // 這里檢測是否是第一個手指按下
        bool isDown = maskedAction == AMOTION_EVENT_ACTION_DOWN;
        // 1. 對于 DOWN 事件,根據(jù)觸摸事件的x,y坐標(biāo),尋找觸摸窗口
        // 參數(shù) addOutsideTargets 表示,只有在第一個手指按下時,如果沒有找到觸摸的窗口,
        // 那么需要保存那些可以接受 OUTSIZE 事件的窗口到 tempTouchState
        newTouchedWindowHandle =
                findTouchedWindowAtLocked(displayId, x, y, &tempTouchState,
                                          isDown /*addOutsideTargets*/, true /*addPortalWindows*/);
        // 省略 ... 處理窗口異常的情況 ...
        // 2. 獲取所有的 getsture monitor
        const std::vector<TouchedMonitor> newGestureMonitors = isDown
                ? selectResponsiveMonitorsLocked(
                          findTouchedGestureMonitorsLocked(displayId, tempTouchState.portalWindows))
                : tempTouchState.gestureMonitors;
        // 既沒有找到觸摸點所在的窗口,也沒有找到 gesture monitor,那么此次尋找觸摸窗口的任務(wù)就失敗了
        if (newTouchedWindowHandle == nullptr && newGestureMonitors.empty()) {
            ALOGI("Dropping event because there is no touchable window or gesture monitor at "
                  "(%d, %d) in display %" PRId32 ".",
                  x, y, displayId);
            injectionResult = InputEventInjectionResult::FAILED;
            goto Failed;
        }
        // 走到這里,表示找到了觸摸的窗口,或者找到 gesture monitor
        if (newTouchedWindowHandle != nullptr) {
            // 馬上要保存窗口了,現(xiàn)在獲取窗口的 flag
            int32_t targetFlags = InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS;
            if (isSplit) {
                targetFlags |= InputTarget::FLAG_SPLIT;
            }
            if (isWindowObscuredAtPointLocked(newTouchedWindowHandle, x, y)) {
                targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
            } else if (isWindowObscuredLocked(newTouchedWindowHandle)) {
                targetFlags |= InputTarget::FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
            }
            // Update hover state.
            if (maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT) {
                newHoverWindowHandle = nullptr;
            } else if (isHoverAction) {
                newHoverWindowHandle = newTouchedWindowHandle;
            }
            // Update the temporary touch state.
            // 如果窗口支持 split,那么用 tempTouchState 保存窗口的時候,要特別保存 pointer id
            BitSet32 pointerIds;
            if (isSplit) {
                uint32_t pointerId = entry.pointerProperties[pointerIndex].id;
                pointerIds.markBit(pointerId);
            }
            // 3. tempTouchState 保存找到的觸摸的窗口
            // 如果是真的找到的觸摸窗口,那么這里就是保存,如果是找到可以接受 OUTSIDE 的窗口,那么這里是更新
            tempTouchState.addOrUpdateWindow(newTouchedWindowHandle, targetFlags, pointerIds);
        } else if (tempTouchState.windows.empty()) {
            // If no window is touched, set split to true. This will allow the next pointer down to
            // be delivered to a new window which supports split touch.
            tempTouchState.split = true;
        }
        if (isDown) {
            // tempTouchState 保存所有的 gesture monitor
            // 4. 第一個手指按下時,tempTouchState 保存 gesture monitor
            tempTouchState.addGestureMonitors(newGestureMonitors);
        }
    } else {
        // ...
    }
    if (newHoverWindowHandle != mLastHoverWindowHandle) {
        // ....
    }
    {
        // 權(quán)限檢測 ...
    }
    // 保存接收 AMOTION_EVENT_ACTION_OUTSIDE 的窗口
    if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
        // ...
    }
    // 第一個手指按下時,保存壁紙窗口
    if (maskedAction == AMOTION_EVENT_ACTION_DOWN) { // 
        // ...
    }
    // 走到這里,表示沒有異常情況了
    injectionResult = InputEventInjectionResult::SUCCEEDED;
    // 5. 把 tempTouchState 保存了觸摸窗口和gesture monitor,保存到 inputTargets 中
    for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
        addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
                              touchedWindow.pointerIds, inputTargets);
    }
    for (const TouchedMonitor& touchedMonitor : tempTouchState.gestureMonitors) {
        addMonitoringTargetLocked(touchedMonitor.monitor, touchedMonitor.xOffset,
                                  touchedMonitor.yOffset, inputTargets);
    }
    // Drop the outside or hover touch windows since we will not care about them
    // in the next iteration.
    tempTouchState.filterNonAsIsTouchWindows();
Failed:
    // ...
    // 6. 緩存 tempTouchState
    if (maskedAction != AMOTION_EVENT_ACTION_SCROLL) {
        if (tempTouchState.displayId >= 0) {
            mTouchStatesByDisplay[displayId] = tempTouchState;
        } else {
            mTouchStatesByDisplay.erase(displayId);
        }
    } 
    return injectionResult;
}

為觸摸事件尋找觸摸窗口的過程,極其復(fù)雜。雖然這段代碼被我省略了很多過程,但是我估計讀者也會看得頭暈。

對于 DOWN 事件

  • 根據(jù) x,y 坐標(biāo)尋找觸摸的窗口。參考【2.1.1 根據(jù)坐標(biāo)找到觸摸窗口】
  • 獲取所有的 gesture monitor 窗口 。
  • 把觸摸窗口保存到 tempTouchState 中。
  • 把所有的 gesture monitor 窗口保存到 tempTouchState 中。
  • 為 tempTouchState 保存所有窗口,創(chuàng)建 InputTarget 對象,并保存到參數(shù) inputTargets 中。參考【2.1.2 保存窗口】
  • 使用 mTouchStatesByDisplay 緩存 tempTouchState。

gesture monitor 是為了實現(xiàn)手勢功能而添加的一個窗口。什么是手勢功能? 例如在屏幕的左邊/右邊,向屏幕中央滑動,會觸發(fā)返回手勢。這個手勢功能用來替代導(dǎo)航鍵。在下一篇文章中,我會剖析這個手勢功能的原理。

對于非 DOWN 事件,一般為 MOVE, UP 事件

  • 獲取 DOWN 事件緩存的 tempTouchState。 因為 tempTouchState 保存了處理 DOWN 事件的觸摸窗口和 gesture monitor,非 DOWN 事件,也會發(fā)送給這些窗口。
  • 重復(fù) DOWN 事件的第5步。

當(dāng)分析的代碼量很大的時候,我們需要有一個整體的觀念。為觸摸事件尋找觸摸窗口,最終的結(jié)果就是把找到的窗口保存到參數(shù) inputTargets 中,后面會把事件分發(fā)給 inputTargets 保存的窗口。

2.1.1 根據(jù)坐標(biāo)找到觸摸窗口

// addOutsideTargets 在第一個手指按下是為 true
// addPortalWindows 值為 true
// ignoreDragWindow 默認為 false
sp<InputWindowHandle> InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, int32_t x,
                                                                 int32_t y, TouchState* touchState,
                                                                 bool addOutsideTargets,
                                                                 bool addPortalWindows,
                                                                 bool ignoreDragWindow) {
    if ((addPortalWindows || addOutsideTargets) && touchState == nullptr) {
        LOG_ALWAYS_FATAL(
                "Must provide a valid touch state if adding portal windows or outside targets");
    }
    // Traverse windows from front to back to find touched window.
    // 從前到后,遍歷窗口
    const std::vector<sp<InputWindowHandle>>& windowHandles = getWindowHandlesLocked(displayId);
    for (const sp<InputWindowHandle>& windowHandle : windowHandles) {
        // ignoreDragWindow 默認為 false
        if (ignoreDragWindow && haveSameToken(windowHandle, mDragState->dragWindow)) {
            continue;
        }
        // 獲取窗口信息
        const InputWindowInfo* windowInfo = windowHandle->getInfo();
        // 匹配屬于特定屏幕的窗口
        if (windowInfo->displayId == displayId) {
            auto flags = windowInfo->flags;
            // 窗口要可見
            if (windowInfo->visible) {
                // 窗口要可觸摸
                if (!flags.test(InputWindowInfo::Flag::NOT_TOUCHABLE)) {
                    // 檢測是否為觸摸模型: 可獲取焦點,并且不允許窗口之外的觸摸事件發(fā)送到它后面的窗口
                    bool isTouchModal = !flags.test(InputWindowInfo::Flag::NOT_FOCUSABLE) &&
                            !flags.test(InputWindowInfo::Flag::NOT_TOUCH_MODAL);
                    // 窗口是觸摸模型,或者觸摸的坐標(biāo)點落在窗口上
                    if (isTouchModal || windowInfo->touchableRegionContainsPoint(x, y)) {
                        int32_t portalToDisplayId = windowInfo->portalToDisplayId;
                        // 如果是 portal window
                        if (portalToDisplayId != ADISPLAY_ID_NONE &&
                            portalToDisplayId != displayId) {
                            if (addPortalWindows) {
                                // For the monitoring channels of the display.
                                // touchState 保存 portal window
                                touchState->addPortalWindow(windowHandle);
                            }
                            // 遞歸調(diào)用,獲取 portal display id 下的觸摸窗口
                            return findTouchedWindowAtLocked(portalToDisplayId, x, y, touchState,
                                                             addOutsideTargets, addPortalWindows);
                        }
                        // 不是 portal window,直接返回找到的窗口
                        return windowHandle;
                    }
                }
                // 走到這里,表示沒有找到觸摸窗口。也就是說,既沒有找到觸摸模型的窗口,也沒有找到包含觸摸點的窗口
                // 當(dāng)?shù)谝粋€手指按下是,addOutsideTargets 值為 true
                // NOT_TOUCH_MODAL 和 WATCH_OUTSIDE_TOUCH 一起使用,當(dāng)?shù)谝粋€手指按下時,如果落在窗口之外
                // 窗口會收到 MotionEvent.ACTION_OUTSIDE 事件
                if (addOutsideTargets && flags.test(InputWindowInfo::Flag::WATCH_OUTSIDE_TOUCH)) {
                    touchState->addOrUpdateWindow(windowHandle,
                                                  InputTarget::FLAG_DISPATCH_AS_OUTSIDE,
                                                  BitSet32(0));
                }
            }
        }
    }
    return nullptr;
}

這里涉及一個 portal window 的概念,由于我沒有找到具體使用的地方,我大致猜測它的意思就是,設(shè)備外接一個屏幕,然后在主屏幕上顯示一個窗口來操作這個外接屏幕。后面的分析,我將略過 portal window 的部分。當(dāng)然,觸摸掌握了觸摸事件的分發(fā)流程,以后遇到了 portal window 的事情,再來分析,應(yīng)該沒問題的。

尋找觸摸點所在的窗口,其實就是從上到下遍歷所有窗口,然后找到滿足條件的窗口。

窗口首先要滿足前置條件

  • 窗口要在指定屏幕上。
  • 窗口要可見。
  • 窗口要可觸摸。

滿足了所有的前置條件后,只要滿足以下任意一個條件,那么就找到了觸摸點所在的窗口

  • 是觸摸模型的窗口: 可獲取焦點,并且不允許窗口之外的觸摸事件發(fā)送到它后面的窗口。
  • 觸摸點的 x,y 坐標(biāo)落在窗口坐標(biāo)系中。

2.1.2 保存窗口

// InputDispatcher 保存觸摸窗口
void InputDispatcher::addWindowTargetLocked(const sp<InputWindowHandle>& windowHandle,
                                            int32_t targetFlags, BitSet32 pointerIds,
                                            std::vector<InputTarget>& inputTargets) {
    std::vector<InputTarget>::iterator it =
            std::find_if(inputTargets.begin(), inputTargets.end(),
                         [&windowHandle](const InputTarget& inputTarget) {
                             return inputTarget.inputChannel->getConnectionToken() ==
                                     windowHandle->getToken();
                         });
    const InputWindowInfo* windowInfo = windowHandle->getInfo();
    // 創(chuàng)建 InputTarget,并保存到參數(shù) inputTargets
    if (it == inputTargets.end()) {
        InputTarget inputTarget;
        std::shared_ptr<InputChannel> inputChannel =
                getInputChannelLocked(windowHandle->getToken());
        if (inputChannel == nullptr) {
            ALOGW("Window %s already unregistered input channel", windowHandle->getName().c_str());
            return;
        }
        inputTarget.inputChannel = inputChannel;
        inputTarget.flags = targetFlags;
        inputTarget.globalScaleFactor = windowInfo->globalScaleFactor;
        inputTarget.displaySize =
                int2(windowHandle->getInfo()->displayWidth, windowHandle->getInfo()->displayHeight);
        inputTargets.push_back(inputTarget);
        it = inputTargets.end() - 1;
    }
    ALOG_ASSERT(it->flags == targetFlags);
    ALOG_ASSERT(it->globalScaleFactor == windowInfo->globalScaleFactor);
    // 保存 InputTarget 后,在保存窗口的坐標(biāo)轉(zhuǎn)換參數(shù),
    // 這個參數(shù)可以把顯示屏的坐標(biāo),轉(zhuǎn)換為窗口的坐標(biāo)
    it->addPointers(pointerIds, windowInfo->transform);
}
// InputDispatcher 保存 gesture monitor
void InputDispatcher::addMonitoringTargetLocked(const Monitor& monitor, float xOffset,
                                                float yOffset,
                                                std::vector<InputTarget>& inputTargets) {
    InputTarget target;
    target.inputChannel = monitor.inputChannel;
    target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
    ui::Transform t;
    t.set(xOffset, yOffset);
    target.setDefaultPointerTransform(t);
    inputTargets.push_back(target);
}

對于觸摸事件,無論是觸摸窗口,還是 gesture monitor,都會被轉(zhuǎn)化為 InputTarget,然后保存到參數(shù) inputTargets 中。當(dāng)后面啟動分發(fā)循環(huán)后,觸摸事件就會發(fā)送到 inputTargets 保存的窗口中。

結(jié)束

本文從整體上分析了觸摸事件的分發(fā)過程,很多細節(jié)并沒有深入去分析,例如,當(dāng)窗口無響應(yīng)時,如何優(yōu)化事件分發(fā)。但是,只要你掌握了基本的流程,這些細節(jié)你可以自行分析。

本文的某些分析過程,跨度可能很大,那是因為這些知識已經(jīng)在前面的文章中講過,如果你閱讀本文,感覺有點困難,那么請先閱讀前面的文章,打好基礎(chǔ)。

理論的文章總有一些枯燥,但是不妨礙我繼續(xù)向前,下一篇文章,將以此為基礎(chǔ),分析那個代替系統(tǒng)導(dǎo)航欄的手勢功能是如何實現(xiàn)的,這也將作為 Input 系統(tǒng)的收官之作。

以上就是Android開發(fā)Input系統(tǒng)觸摸事件分發(fā)的詳細內(nèi)容,更多關(guān)于Android Input觸摸事件分發(fā)的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Android Menu詳解及示例代碼

    Android Menu詳解及示例代碼

    本文主要介紹Android Menu,這里對Android菜單(menu)進行了詳細的介紹,并給出示例代碼和實現(xiàn)效果圖,有需要的小伙伴可以參考下
    2016-08-08
  • docker網(wǎng)絡(luò)配置過程詳解介紹

    docker網(wǎng)絡(luò)配置過程詳解介紹

    大家好,本篇文章主要講的是docker網(wǎng)絡(luò)配置過程詳解介紹,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下,方便下次瀏覽
    2021-12-12
  • Android實現(xiàn)手機定位的案例代碼

    Android實現(xiàn)手機定位的案例代碼

    今天小編就為大家分享一篇關(guān)于Android實現(xiàn)手機定位的案例代碼,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-03-03
  • android端使用openCV實現(xiàn)車牌檢測

    android端使用openCV實現(xiàn)車牌檢測

    這篇文章主要為大家詳細介紹了android端使用openCV實現(xiàn)車牌檢測,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-12-12
  • android實現(xiàn)小音頻頻繁播放

    android實現(xiàn)小音頻頻繁播放

    這篇文章主要為大家詳細介紹了android實現(xiàn)小音頻頻繁播放,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-01-01
  • Android編程實現(xiàn)狀態(tài)保存的方法分析

    Android編程實現(xiàn)狀態(tài)保存的方法分析

    這篇文章主要介紹了Android編程實現(xiàn)狀態(tài)保存的方法,結(jié)合實例形式分析了Android狀態(tài)保存的原理、實現(xiàn)方法與相關(guān)注意事項,需要的朋友可以參考下
    2017-08-08
  • 輕松實現(xiàn)Android3D效果通俗易懂

    輕松實現(xiàn)Android3D效果通俗易懂

    前幾天有粉絲要求計蒙寫一個3d效果的簡單教程,其實這個在Android官方demo中是有的,可能對于新手而言看不太明白,于是根據(jù)本人自己的理解來寫一個教程,并改成粉絲要求的樣子
    2021-08-08
  • Android中 自定義數(shù)據(jù)綁定適配器BaseAdapter的方法

    Android中 自定義數(shù)據(jù)綁定適配器BaseAdapter的方法

    本篇文章小編為大家介紹,Android中 自定義數(shù)據(jù)綁定適配器BaseAdapter的方法。需要的朋友參考下
    2013-04-04
  • Android 自定義Switch開關(guān)按鈕的樣式實例詳解

    Android 自定義Switch開關(guān)按鈕的樣式實例詳解

    本文主要講的是在Android原生Switch控件的基礎(chǔ)上進行樣式自定義,內(nèi)容很簡單,但是在實現(xiàn)的過程中還是遇到了一些問題,在此記錄下來,需要的朋友參考下吧
    2017-12-12
  • android使用ViewPager組件實現(xiàn)app引導(dǎo)查看頁面

    android使用ViewPager組件實現(xiàn)app引導(dǎo)查看頁面

    這篇文章主要為大家詳細介紹了android使用ViewPager組件實現(xiàn)app引導(dǎo)查看頁面,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-07-07

最新評論