Android開(kāi)發(fā)Input系統(tǒng)觸摸事件分發(fā)
引言
Input系統(tǒng): InputReader 處理觸摸事件 分析了 InputReader 對(duì)觸摸事件的處理流程,最終的結(jié)果是把觸摸事件包裝成 NotifyMotionArgs,然后分發(fā)給下一環(huán)。根據(jù) Input系統(tǒng): InputManagerService的創(chuàng)建與啟動(dòng) 可知,下一環(huán)是 InputClassifier。然而系統(tǒng)目前并不支持 InputClassifier 的功能,因此事件會(huì)被直接發(fā)送到 InputDispatcher。
Input系統(tǒng): 按鍵事件分發(fā) 分析了按鍵事件的分發(fā)流程,雖然分析的目標(biāo)是按鍵事件,但是也從整體上,描繪了事件分發(fā)的框架。而本文分析觸摸事件的分發(fā)流程,也會(huì)用到這個(gè)框架,因此重復(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;
// 來(lái)自InputReader/InputClassifier的 motion 事件,都是受信任的
policyFlags |= POLICY_FLAG_TRUSTED;
android::base::Timer t;
// 1. 對(duì)觸摸事件執(zhí)行截?cái)嗖呗?
// 觸摸事件入隊(duì)前,查詢截?cái)嗖呗?,查詢的結(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 收到觸摸事件后的處理流程,與收到按鍵事件的處理流程非常相似
- 對(duì)觸摸事件進(jìn)行截?cái)嗖呗圆樵?。參考?.1 截?cái)嗖呗圆樵儭?/li>
- 把觸摸事件加入 InputDispatcher 收件箱,然后喚醒線程處理觸摸事件。
1.1 截?cái)嗖呗圆樵?/h3>
void NativeInputManager::interceptMotionBeforeQueueing(const int32_t displayId, nsecs_t when,
uint32_t& policyFlags) {
bool interactive = mInteractive.load();
if (interactive) {
policyFlags |= POLICY_FLAG_INTERACTIVE;
}
// 受信任,并且是非注入的事件
if ((policyFlags & POLICY_FLAG_TRUSTED) && !(policyFlags & POLICY_FLAG_INJECTED)) {
if (policyFlags & POLICY_FLAG_INTERACTIVE) {
// 設(shè)備處于交互狀態(tài)下,受信任且非注入的事件,直接發(fā)送給用戶,而不經(jīng)過(guò)截?cái)嗖呗蕴幚?
policyFlags |= POLICY_FLAG_PASS_TO_USER;
} else {
// 只有設(shè)備處于非交互狀態(tài),觸摸事件才需要執(zhí)行截?cái)嗖呗?
JNIEnv* env = jniEnv();
jint wmActions = env->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& policyFlags) {
if (wmActions & WM_ACTION_PASS_TO_USER) {
policyFlags |= POLICY_FLAG_PASS_TO_USER;
}
}
void NativeInputManager::interceptMotionBeforeQueueing(const int32_t displayId, nsecs_t when,
uint32_t& policyFlags) {
bool interactive = mInteractive.load();
if (interactive) {
policyFlags |= POLICY_FLAG_INTERACTIVE;
}
// 受信任,并且是非注入的事件
if ((policyFlags & POLICY_FLAG_TRUSTED) && !(policyFlags & POLICY_FLAG_INJECTED)) {
if (policyFlags & POLICY_FLAG_INTERACTIVE) {
// 設(shè)備處于交互狀態(tài)下,受信任且非注入的事件,直接發(fā)送給用戶,而不經(jīng)過(guò)截?cái)嗖呗蕴幚?
policyFlags |= POLICY_FLAG_PASS_TO_USER;
} else {
// 只有設(shè)備處于非交互狀態(tài),觸摸事件才需要執(zhí)行截?cái)嗖呗?
JNIEnv* env = jniEnv();
jint wmActions = env->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& policyFlags) {
if (wmActions & WM_ACTION_PASS_TO_USER) {
policyFlags |= POLICY_FLAG_PASS_TO_USER;
}
}
一個(gè)觸摸事件,必須滿足下面三種情況,才執(zhí)行截?cái)嗖呗?/p>
- 觸摸事件是受信任的。來(lái)自輸入設(shè)備的觸摸事件都是受信任的。
- 觸摸事件是非注入的。monkey 的原理就是注入觸摸事件,因此它的事件是不需要經(jīng)過(guò)截?cái)嗖呗蕴幚淼摹?/li>
- 設(shè)備處于非交互狀態(tài)。一般來(lái)說(shuō),非交互狀態(tài)指的就是顯示屏處于滅屏狀態(tài)。
另外還需要關(guān)注的是,事件在什么時(shí)候是不需要經(jīng)過(guò)截?cái)嗖呗?,有兩種情況
- 對(duì)于受信任且非注入的觸摸事件,如果設(shè)備處于交互狀態(tài),直接發(fā)送給用戶。 也就是說(shuō),如果顯示屏處于亮屏狀態(tài),輸入設(shè)備產(chǎn)生的觸摸事件一定會(huì)發(fā)送給窗口。
- 對(duì)于不受信任,或者注入的觸摸事件,如果設(shè)備處于交互狀態(tài),也是直接發(fā)送給用戶。也就是說(shuō),如果顯示屏處于亮屏狀態(tài),monkey 注入的觸摸事件,也是直接發(fā)送給窗口的。
最后還要注意一件事,如果一個(gè)觸摸事件是不受信任的事件,或者是注入事件,當(dāng)設(shè)備處于非交互狀態(tài)下(通常指滅屏),那么它不經(jīng)過(guò)截?cái)嗖呗?,也不?huì)發(fā)送給用戶,也就是會(huì)被丟棄。
在實(shí)際工作中處理的觸摸事件,通常都是來(lái)自輸入設(shè)備,它肯定是受信任的,而且非注入的,因此它只有在設(shè)備處于非交互狀態(tài)下(一般指滅屏)下,非會(huì)執(zhí)行截?cái)嗖呗裕绻O(shè)備處于交互狀態(tài)(通常指亮屏),會(huì)被直接分發(fā)給窗口。
現(xiàn)在來(lái)看下截?cái)嗖呗缘木唧w實(shí)現(xiàn)
// PhoneWindowManager.java
public int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos,
int policyFlags) {
// 1. 如果策略要求喚醒屏幕,那么截?cái)噙@個(gè)觸摸事件
// 一般來(lái)說(shuō),喚醒屏幕的策略取決于設(shè)備的配置文件
if ((policyFlags & FLAG_WAKE) != 0) {
if (wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromMotion,
PowerManager.WAKE_REASON_WAKE_MOTION, "android.policy:MOTION")) {
// 返回 0,表示截?cái)嘤|摸事件
return 0;
}
}
// 2. 判斷非交互狀態(tài)下,是否截?cái)嗍录?
if (shouldDispatchInputWhenNonInteractive(displayId, KEYCODE_UNKNOWN)) {
// 返回這個(gè)值,表示不截?cái)嗍录簿褪鞘录职l(fā)給用戶
return ACTION_PASS_TO_USER;
}
// 忽略 theater mode
if (isTheaterModeEnabled() && (policyFlags & FLAG_WAKE) != 0) {
wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromMotionWhenNotDreaming,
PowerManager.WAKE_REASON_WAKE_MOTION, "android.policy:MOTION");
}
// 3. 默認(rèn)截?cái)嘤|摸事件
// 返回0,表示截?cái)嗍录?
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 && !mHasFeatureWatch) {
return false;
}
// displayOff 表示屏幕處于 off 狀態(tài),但是非 off 狀態(tài),并不表示一定是亮屏狀態(tài)
// 對(duì)于 doze 狀態(tài),屏幕處于 on 狀態(tài),但是屏幕可能仍然是黑的
// 因此,只要屏幕處于 on 狀態(tài),并且顯示了鎖屏,觸摸事件不會(huì)截?cái)?
if (isKeyguardShowingAndNotOccluded() && !displayOff) {
return true;
}
// 對(duì)于觸摸事件,keyCode 的值為 KEYCODE_UNKNOWN
if (mHasFeatureWatch && (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;
}
// 對(duì)于默認(rèn)屏幕,如果設(shè)備處于夢(mèng)境狀態(tài),那么觸摸事件不截?cái)?
// 因?yàn)?doze 組件需要接收觸摸事件,可能會(huì)喚醒屏幕
if (isDefaultDisplay) {
IDreamManager dreamManager = getDreamManager();
try {
if (dreamManager != null && 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;
}
截?cái)嗖呗允欠窠財(cái)嘤|摸事件,取決于策略的返回值,有兩種情況
- 返回 0,表示截?cái)嘤|摸事件。
- 返回 ACTION_PASS_TO_USER ,表示不截?cái)嘤|摸事件,也就是把觸摸事件分發(fā)給用戶/窗口。
下面列舉觸摸事件截?cái)嗯c否的情況,但是要注意一個(gè)前提,設(shè)備處于非交互狀態(tài)(一般就是指滅屏狀態(tài))
- 事件會(huì)被傳遞給用戶,也就是不截?cái)?,情況如下
- 有鎖屏,并且顯示屏處于非 off 狀態(tài)。注意,非 off 狀態(tài),并不是表示屏幕處于 on(亮屏) 狀態(tài),也可能是 doze 狀態(tài)(屏幕處于低電量狀態(tài)),doze 狀態(tài)屏幕也是黑的。
- 夢(mèng)境狀態(tài)。因?yàn)閴?mèng)境狀態(tài)下會(huì)運(yùn)行 doze 組件。
- 事件被截?cái)?,情況如下
- 策略標(biāo)志位包含 FLAG_WAKE ,它會(huì)導(dǎo)致屏幕被喚醒,因此需要截?cái)嘤|摸事件。FLAG_WAKE 一般來(lái)自于輸入設(shè)備的配置文件。
- 沒(méi)有鎖屏,沒(méi)有夢(mèng)境,也沒(méi)有 FLAG_WAKE,默認(rèn)就會(huì)截?cái)唷?/li>
從上面的分析可以總結(jié)出了兩條結(jié)論
- 如果系統(tǒng)有組件在運(yùn)行,例如,鎖屏、doze組件,那么觸摸事件需要分發(fā)到這些組件,因此不會(huì)被截?cái)唷?/li>
- 如果沒(méi)有組件運(yùn)行,觸摸事件都會(huì)被截?cái)唷S|摸事件由于需要喚醒屏幕,而導(dǎo)致被截?cái)啵皇瞧渲幸粋€(gè)特例。
2. InputDispatcher 分發(fā)觸摸事件
由 Input系統(tǒng): InputManagerService的創(chuàng)建與啟動(dòng) 可知,InputDispatcher 通過(guò)線程循環(huán)來(lái)處理收件箱中的事件,而且一次循環(huán)只能處理一個(gè)事件
void InputDispatcher::dispatchOnce() {
nsecs_t nextWakeupTime = LONG_LONG_MAX;
{ // acquire lock
std::scoped_lock _l(mLock);
mDispatcherIsAlive.notify_all();
if (!haveCommandsLocked()) {
// 1. 分發(fā)一個(gè)觸摸事件
dispatchOnceInnerLocked(&nextWakeupTime);
}
// 觸摸事件的分發(fā)過(guò)程不會(huì)產(chǎn)生命令
if (runCommandsLockedInterruptible()) {
nextWakeupTime = LONG_LONG_MIN;
}
// 2. 計(jì)算線程下次喚醒的時(shí)間點(diǎn),以便處理 anr
const nsecs_t nextAnrCheck = processAnrsLocked();
nextWakeupTime = std::min(nextWakeupTime, nextAnrCheck);
if (nextWakeupTime == LONG_LONG_MAX) {
mDispatcherEnteredIdle.notify_all();
}
} // release lock
// 3. 線程休眠指定的時(shí)長(zhǎng)
nsecs_t currentTime = now();
int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
mLooper->pollOnce(timeoutMillis);
}
一次線程循環(huán)處理觸摸事件的過(guò)程如下
- 分發(fā)一個(gè)觸摸事件。
- 當(dāng)事件分發(fā)給窗口后,會(huì)計(jì)算一個(gè)窗口反饋的超時(shí)時(shí)間,利用這個(gè)時(shí)間,計(jì)算線程下次喚醒的時(shí)間點(diǎn)。
- 利用上一步計(jì)算出的線程喚醒的時(shí)間點(diǎn),計(jì)算出線程最終需要休眠多長(zhǎng)時(shí)間。當(dāng)線程被喚醒后,會(huì)檢查接收觸摸時(shí)間的窗口,是否反饋超時(shí),如果超時(shí),會(huì)引發(fā) ANR。
現(xiàn)在來(lái)看看如何分發(fā)一個(gè)觸摸事件
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
nsecs_t currentTime = now();
if (!mDispatchEnabled) {
resetKeyRepeatLocked();
}
if (mDispatchFrozen) {
return;
}
// 這里是優(yōu)化 app 切換的延遲
// mAppSwitchDueTime 是 app 切換的超時(shí)時(shí)間,如果小于當(dāng)前時(shí)間,那么表明app切換超時(shí)了
// 如果app切換超時(shí),那么在app切換按鍵事件之前的未處理的事件,都將會(huì)被丟棄
bool isAppSwitchDue = mAppSwitchDueTime <= currentTime;
if (mAppSwitchDueTime < *nextWakeupTime) {
*nextWakeupTime = mAppSwitchDueTime;
}
// mPendingEvent 表示正在處理的事件
if (!mPendingEvent) {
if (mInboundQueue.empty()) {
// ...
} else {
// 1. 從收件箱隊(duì)列中取出事件
mPendingEvent = mInboundQueue.front();
mInboundQueue.pop_front();
traceInboundQueueLengthLocked();
}
// 如果這個(gè)事件需要傳遞給用戶,那么需要同上層的 PowerManagerService,此時(shí)有用戶行為,這個(gè)作用就是延長(zhǎng)亮屏的時(shí)間
if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) {
pokeUserActivityLocked(*mPendingEvent);
}
}
ALOG_ASSERT(mPendingEvent != nullptr);
bool done = false;
// 檢測(cè)丟棄事件的原因
DropReason dropReason = DropReason::NOT_DROPPED;
if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) {
// 被截?cái)嗖呗越財(cái)?
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 切換超時(shí),導(dǎo)致觸摸事件被丟棄
dropReason = DropReason::APP_SWITCH;
}
if (dropReason == DropReason::NOT_DROPPED && isStaleEvent(currentTime, *motionEntry)) {
// 10s 之前的事件,已經(jīng)過(guò)期
dropReason = DropReason::STALE;
}
// 這里是優(yōu)化應(yīng)用無(wú)響應(yīng)的一個(gè)措施,會(huì)丟棄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ā)送完畢,都會(huì)返回 true
// 返回 false,表示暫時(shí)不知道如何處理事件,因此線程會(huì)休眠
// 然后,線程再次被喚醒時(shí),再來(lái)處理這個(gè)事件
if (done) {
if (dropReason != DropReason::NOT_DROPPED) {
dropInboundEventLocked(*mPendingEvent, dropReason);
}
mLastDropReason = dropReason;
// 重置 mPendingEvent
releasePendingEventLocked();
// 立即喚醒,處理下一個(gè)事件
*nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately
}
}
Input系統(tǒng): 按鍵事件分發(fā) 已經(jīng)分析過(guò) InputDispatcher 的線程循環(huán)。而對(duì)于觸摸事件,是通過(guò) InputDispatcher::dispatchMotionLocked() 進(jìn)行分發(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,表示暫時(shí)不知道如何處理這個(gè)事件,這會(huì)導(dǎo)致線程休眠
// 等線程下次被喚醒時(shí),再來(lái)處理這個(gè)事件
return false;
}
// 走到這里,表示觸摸事件已經(jīng)被處理,因此保存處理的結(jié)果
// 只要返回的不是 InputEventInjectionResult::PENDING
// 都表示事件被處理,無(wú)論是權(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 中
// 開(kāi)發(fā)者選項(xiàng)中的 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;
}
一個(gè)觸摸事件的分發(fā)過(guò)程,可以大致總結(jié)為以下幾個(gè)過(guò)程
- 如果有原因表明觸摸事件需要被丟棄,那么觸摸事件不會(huì)走后面的分發(fā)流程,即被丟棄。
- 通常觸摸事件是發(fā)送給窗口的,因此需要為觸摸事件尋找觸摸窗口。窗口最終被保存到 inputTargets 中。參考【2.1 尋找觸摸的窗口】
- inputTargets 保存觸摸窗口后,還要保存 global monitor 窗口。例如開(kāi)發(fā)者選項(xiàng)中的 Show taps 和 Pointer location,就是利用這個(gè)窗口實(shí)現(xiàn)的。
- 啟動(dòng)分發(fā)循環(huán),把觸摸事件分發(fā)給 inputTargets 保存的窗口。 由于 Input系統(tǒng): 按鍵事件分發(fā) 已經(jīng)分發(fā)過(guò)這個(gè)過(guò)程,本文不再分析。
2.1 尋找觸摸的窗口
InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked(
nsecs_t currentTime, const MotionEntry& entry, std::vector<InputTarget>& inputTargets,
nsecs_t* nextWakeupTime, bool* outConflictingPointerActions) {
// ...
// 6. 對(duì)于非 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);
}
// ...
// 第一個(gè)條件 newGesture 表示第一個(gè)手指按下
// 后面一個(gè)條件,表示當(dāng)前窗口支持 split motion,并且此時(shí)有另外一個(gè)手指按下
if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
/* Case 1: New splittable pointer going down, or need target for hover or scroll. */
// 觸摸點(diǎn)的獲取 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));
}
// 這里檢測(cè)是否是第一個(gè)手指按下
bool isDown = maskedAction == AMOTION_EVENT_ACTION_DOWN;
// 1. 對(duì)于 DOWN 事件,根據(jù)觸摸事件的x,y坐標(biāo),尋找觸摸窗口
// 參數(shù) addOutsideTargets 表示,只有在第一個(gè)手指按下時(shí),如果沒(méi)有找到觸摸的窗口,
// 那么需要保存那些可以接受 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;
// 既沒(méi)有找到觸摸點(diǎn)所在的窗口,也沒(méi)有找到 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 保存窗口的時(shí)候,要特別保存 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. 第一個(gè)手指按下時(shí),tempTouchState 保存 gesture monitor
tempTouchState.addGestureMonitors(newGestureMonitors);
}
} else {
// ...
}
if (newHoverWindowHandle != mLastHoverWindowHandle) {
// ....
}
{
// 權(quán)限檢測(cè) ...
}
// 保存接收 AMOTION_EVENT_ACTION_OUTSIDE 的窗口
if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
// ...
}
// 第一個(gè)手指按下時(shí),保存壁紙窗口
if (maskedAction == AMOTION_EVENT_ACTION_DOWN) { //
// ...
}
// 走到這里,表示沒(méi)有異常情況了
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;
}
為觸摸事件尋找觸摸窗口的過(guò)程,極其復(fù)雜。雖然這段代碼被我省略了很多過(guò)程,但是我估計(jì)讀者也會(huì)看得頭暈。
對(duì)于 DOWN 事件
- 根據(jù) x,y 坐標(biāo)尋找觸摸的窗口。參考【2.1.1 根據(jù)坐標(biāo)找到觸摸窗口】
- 獲取所有的 gesture monitor 窗口 。
- 把觸摸窗口保存到 tempTouchState 中。
- 把所有的 gesture monitor 窗口保存到 tempTouchState 中。
- 為 tempTouchState 保存所有窗口,創(chuàng)建 InputTarget 對(duì)象,并保存到參數(shù) inputTargets 中。參考【2.1.2 保存窗口】
- 使用 mTouchStatesByDisplay 緩存 tempTouchState。
gesture monitor 是為了實(shí)現(xiàn)手勢(shì)功能而添加的一個(gè)窗口。什么是手勢(shì)功能? 例如在屏幕的左邊/右邊,向屏幕中央滑動(dòng),會(huì)觸發(fā)返回手勢(shì)。這個(gè)手勢(shì)功能用來(lái)替代導(dǎo)航鍵。在下一篇文章中,我會(huì)剖析這個(gè)手勢(shì)功能的原理。
對(duì)于非 DOWN 事件,一般為 MOVE, UP 事件
- 獲取 DOWN 事件緩存的 tempTouchState。 因?yàn)?tempTouchState 保存了處理 DOWN 事件的觸摸窗口和 gesture monitor,非 DOWN 事件,也會(huì)發(fā)送給這些窗口。
- 重復(fù) DOWN 事件的第5步。
當(dāng)分析的代碼量很大的時(shí)候,我們需要有一個(gè)整體的觀念。為觸摸事件尋找觸摸窗口,最終的結(jié)果就是把找到的窗口保存到參數(shù) inputTargets 中,后面會(huì)把事件分發(fā)給 inputTargets 保存的窗口。
2.1.1 根據(jù)坐標(biāo)找到觸摸窗口
// addOutsideTargets 在第一個(gè)手指按下是為 true
// addPortalWindows 值為 true
// ignoreDragWindow 默認(rèn)為 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 默認(rèn)為 false
if (ignoreDragWindow && haveSameToken(windowHandle, mDragState->dragWindow)) {
continue;
}
// 獲取窗口信息
const InputWindowInfo* windowInfo = windowHandle->getInfo();
// 匹配屬于特定屏幕的窗口
if (windowInfo->displayId == displayId) {
auto flags = windowInfo->flags;
// 窗口要可見(jiàn)
if (windowInfo->visible) {
// 窗口要可觸摸
if (!flags.test(InputWindowInfo::Flag::NOT_TOUCHABLE)) {
// 檢測(cè)是否為觸摸模型: 可獲取焦點(diǎn),并且不允許窗口之外的觸摸事件發(fā)送到它后面的窗口
bool isTouchModal = !flags.test(InputWindowInfo::Flag::NOT_FOCUSABLE) &&
!flags.test(InputWindowInfo::Flag::NOT_TOUCH_MODAL);
// 窗口是觸摸模型,或者觸摸的坐標(biāo)點(diǎn)落在窗口上
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;
}
}
// 走到這里,表示沒(méi)有找到觸摸窗口。也就是說(shuō),既沒(méi)有找到觸摸模型的窗口,也沒(méi)有找到包含觸摸點(diǎn)的窗口
// 當(dāng)?shù)谝粋€(gè)手指按下是,addOutsideTargets 值為 true
// NOT_TOUCH_MODAL 和 WATCH_OUTSIDE_TOUCH 一起使用,當(dāng)?shù)谝粋€(gè)手指按下時(shí),如果落在窗口之外
// 窗口會(huì)收到 MotionEvent.ACTION_OUTSIDE 事件
if (addOutsideTargets && flags.test(InputWindowInfo::Flag::WATCH_OUTSIDE_TOUCH)) {
touchState->addOrUpdateWindow(windowHandle,
InputTarget::FLAG_DISPATCH_AS_OUTSIDE,
BitSet32(0));
}
}
}
}
return nullptr;
}
這里涉及一個(gè) portal window 的概念,由于我沒(méi)有找到具體使用的地方,我大致猜測(cè)它的意思就是,設(shè)備外接一個(gè)屏幕,然后在主屏幕上顯示一個(gè)窗口來(lái)操作這個(gè)外接屏幕。后面的分析,我將略過(guò) portal window 的部分。當(dāng)然,觸摸掌握了觸摸事件的分發(fā)流程,以后遇到了 portal window 的事情,再來(lái)分析,應(yīng)該沒(méi)問(wèn)題的。
尋找觸摸點(diǎn)所在的窗口,其實(shí)就是從上到下遍歷所有窗口,然后找到滿足條件的窗口。
窗口首先要滿足前置條件
- 窗口要在指定屏幕上。
- 窗口要可見(jiàn)。
- 窗口要可觸摸。
滿足了所有的前置條件后,只要滿足以下任意一個(gè)條件,那么就找到了觸摸點(diǎn)所在的窗口
- 是觸摸模型的窗口: 可獲取焦點(diǎn),并且不允許窗口之外的觸摸事件發(fā)送到它后面的窗口。
- 觸摸點(diǎn)的 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ù),
// 這個(gè)參數(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);
}
對(duì)于觸摸事件,無(wú)論是觸摸窗口,還是 gesture monitor,都會(huì)被轉(zhuǎn)化為 InputTarget,然后保存到參數(shù) inputTargets 中。當(dāng)后面啟動(dòng)分發(fā)循環(huán)后,觸摸事件就會(huì)發(fā)送到 inputTargets 保存的窗口中。
結(jié)束
本文從整體上分析了觸摸事件的分發(fā)過(guò)程,很多細(xì)節(jié)并沒(méi)有深入去分析,例如,當(dāng)窗口無(wú)響應(yīng)時(shí),如何優(yōu)化事件分發(fā)。但是,只要你掌握了基本的流程,這些細(xì)節(jié)你可以自行分析。
本文的某些分析過(guò)程,跨度可能很大,那是因?yàn)檫@些知識(shí)已經(jīng)在前面的文章中講過(guò),如果你閱讀本文,感覺(jué)有點(diǎn)困難,那么請(qǐng)先閱讀前面的文章,打好基礎(chǔ)。
理論的文章總有一些枯燥,但是不妨礙我繼續(xù)向前,下一篇文章,將以此為基礎(chǔ),分析那個(gè)代替系統(tǒng)導(dǎo)航欄的手勢(shì)功能是如何實(shí)現(xiàn)的,這也將作為 Input 系統(tǒng)的收官之作。
以上就是Android開(kāi)發(fā)Input系統(tǒng)觸摸事件分發(fā)的詳細(xì)內(nèi)容,更多關(guān)于Android Input觸摸事件分發(fā)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
docker網(wǎng)絡(luò)配置過(guò)程詳解介紹
大家好,本篇文章主要講的是docker網(wǎng)絡(luò)配置過(guò)程詳解介紹,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下,方便下次瀏覽2021-12-12
Android實(shí)現(xiàn)手機(jī)定位的案例代碼
今天小編就為大家分享一篇關(guān)于Android實(shí)現(xiàn)手機(jī)定位的案例代碼,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-03-03
android端使用openCV實(shí)現(xiàn)車(chē)牌檢測(cè)
這篇文章主要為大家詳細(xì)介紹了android端使用openCV實(shí)現(xiàn)車(chē)牌檢測(cè),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12
Android編程實(shí)現(xiàn)狀態(tài)保存的方法分析
這篇文章主要介紹了Android編程實(shí)現(xiàn)狀態(tài)保存的方法,結(jié)合實(shí)例形式分析了Android狀態(tài)保存的原理、實(shí)現(xiàn)方法與相關(guān)注意事項(xiàng),需要的朋友可以參考下2017-08-08
輕松實(shí)現(xiàn)Android3D效果通俗易懂
前幾天有粉絲要求計(jì)蒙寫(xiě)一個(gè)3d效果的簡(jiǎn)單教程,其實(shí)這個(gè)在Android官方demo中是有的,可能對(duì)于新手而言看不太明白,于是根據(jù)本人自己的理解來(lái)寫(xiě)一個(gè)教程,并改成粉絲要求的樣子2021-08-08
Android中 自定義數(shù)據(jù)綁定適配器BaseAdapter的方法
本篇文章小編為大家介紹,Android中 自定義數(shù)據(jù)綁定適配器BaseAdapter的方法。需要的朋友參考下2013-04-04
Android 自定義Switch開(kāi)關(guān)按鈕的樣式實(shí)例詳解
本文主要講的是在Android原生Switch控件的基礎(chǔ)上進(jìn)行樣式自定義,內(nèi)容很簡(jiǎn)單,但是在實(shí)現(xiàn)的過(guò)程中還是遇到了一些問(wèn)題,在此記錄下來(lái),需要的朋友參考下吧2017-12-12
android使用ViewPager組件實(shí)現(xiàn)app引導(dǎo)查看頁(yè)面
這篇文章主要為大家詳細(xì)介紹了android使用ViewPager組件實(shí)現(xiàn)app引導(dǎo)查看頁(yè)面,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07

