你需要掌握的事件分发高阶知识
戳藍字“CSDN云計算”關注我們哦!
系列前作
1.?Android輸入系統的事件傳遞流程和IMS的誕生
2.?只了解View的事件分發是不夠的,來看下輸入系統對事件的處理
1.InputReader的加工類型
在只了解View的事件分發是不夠的,來看下輸入系統對事件的處理這篇文章中,我們知道InputReader會對原始輸入事件進行加工,如果事件的類型為按鍵類型的事件,就會調用如下一段代碼。
frameworks/native/services/inputflinger/InputDispatcher.cpp
??...
????bool?needWake;
????{?
????...
????}?//?release?lock
????if?(needWake)?{
????????mLooper->wake();
????}
}
InputDispatcher的notifyKey方法用于喚醒InputDispatcherThread,它的參數NotifyKeyArgs是InputReader對按鍵類型的事件加工后得到的。
frameworks/native/services/inputflinger/InputListener.h
????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
#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
????mDispatcher->dispatchOnce();
????return?true;
}
threadLoop函數中只調用了InputDispatcher的dispatchOnce函數:
frameworks/native/services/inputflinger/InputDispatcher.cpp
????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
????...
????//?如果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(下一次InputDispatcher醒來的時間),就將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的分發過程還有一部分沒有講解,這一部分就是事件分發到目標窗口的過程,會在本系列的下一篇文章進行講解。
文章轉自公眾號:劉舒望
— — — END — — —
1.微信群:
添加小編微信:color_ld,備注“進群+姓名+公司職位”即可,加入【云計算學習交流群】,和志同道合的朋友們共同打卡學習!
2.征稿:
投稿郵箱:liudan@csdn.net;微信號:color_ld。請備注投稿+姓名+公司職位。
推薦閱讀
程序員加班很嚴重嗎?看看國外程序員怎么懟老板!
錘子變天?| 暢言
趣店斗魚深陷裁員風波,程序員寒冬何去何從?| 暢言
玩過音樂, "推過"嫩模, 以太坊大神人設崩塌, 有錢任性也抵不過區塊鏈寒冬
【BDTC 2018】PingCAP申礫:做一個真正通用的數據庫產品
谷歌搜索重返中國按下暫停鍵,CEO皮查伊“對決”美國國會
↓點擊“閱讀原文”,打開APP 閱讀更順暢
總結
以上是生活随笔為你收集整理的你需要掌握的事件分发高阶知识的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 联想win10怎么把字体调大 如何在联想
- 下一篇: linux内存管理的主要概念是虚拟内存,