系統(tǒng)機制分析
Android 系統(tǒng)是由事件驅(qū)動的撑刺,而 input 是最常見的事件之一,用戶的點擊挠铲、滑動拂苹、長按等操作瓢棒,都屬于 input 事件驅(qū)動脯宿,其中的核心就是 InputReader 和 InputDispatcher镐侯。InputReader 和 InputDispatcher 是跑在 SystemServer進程中的兩個 native 循環(huán)線程翠语,負責讀取和分發(fā) Input 事件点骑。整個處理過程大致流程如下:
InputReader負責從EventHub里面把Input事件讀取出來黑滴,然后交給 InputDispatcher 進行事件分發(fā)袁辈;
InputDispatcher在拿到 InputReader獲取的事件之后晚缩,對事件進行包裝后,尋找并分發(fā)到目標窗口;
InboundQueue隊列(“iq”)中放著InputDispatcher從InputReader中拿到的input事件鸣皂;
OutboundQueue(“oq”)隊列里面放的是即將要被派發(fā)給各個目標窗口App的事件齐邦;
WaitQueue隊列里面記錄的是已經(jīng)派發(fā)給 App(“wq”)措拇,但是 App還在處理沒有返回處理成功的事件;
PendingInputEventQueue隊列(“aq”)中記錄的是應(yīng)用需要處理的Input事件券犁,這里可以看到input事件已經(jīng)傳遞到了應(yīng)用進程;
deliverInputEvent 標識 AppUI Thread 被 Input 事件喚醒稚新;
InputResponse 標識 Input 事件區(qū)域跪腹,這里可以看到一個 Input_Down 事件 + 若干個 Input_Move 事件 + 一個 Input_Up 事件的處理階段都被算到了這里褂删;
App 響應(yīng)處理Input 事件,內(nèi)部會在其界面View樹中傳遞處理冲茸。
目錄
基礎(chǔ)
1.1 事件分發(fā)的對象
當用戶觸摸屏幕時(View或ViewGroup派生的控件)屯阀,將產(chǎn)生點擊事件(Touch事件)。
Touch事件相關(guān)細節(jié)(發(fā)生觸摸的位置轴术、時間难衰、歷史記錄、手勢動作等)被封裝成MotionEvent對象
主要發(fā)生的Touch事件有如下四種:
MotionEvent.ACTION_DOWN:按下View(所有事件的開始)
MotionEvent.ACTION_MOVE:滑動View
MotionEvent.ACTION_CANCEL:非人為原因結(jié)束本次事件
MotionEvent.ACTION_UP:抬起View(與DOWN對應(yīng))
事件列:從手指接觸屏幕至手指離開屏幕盖袭,這個過程產(chǎn)生的一系列事件 任何事件列都是以DOWN事件開始醇蝴,UP事件結(jié)束霉涨,中間有無數(shù)的MOVE事件往枷,如下圖:
1.2 事件分發(fā)的本質(zhì)
當一個點擊事件發(fā)生后,系統(tǒng)需要將這個事件傳遞給一個具體的View去處理导而。這個事件傳遞的過程就是分發(fā)過程洼滚。
1.3 事件在哪些對象之間進行傳遞
主要涉及Activity享幽、ViewGroup奔坟、View
1.4 事件分發(fā)過程由哪些方法協(xié)作完成
主要有三個方法dispatchTouchEvent() 鸯隅、onInterceptTouchEvent()和onTouchEvent()
詳細流程
通過一張圖理解
1溶推、 Activity
Activity要做的事情是轉(zhuǎn)發(fā)到下層,都沒有消費的情況下,最后由Activity來收尾兜底
2、 ViewGroup
1、是否在容器層面攔截
2、該事件是否要分發(fā)位谋,取消事件不需要消費
3赊淑、傳遞子View消費之前還要看是不是有效的组哩,是點在了哪一個View上
4罐栈、所以需要遍歷所有子View確定是否存在
源碼:
public boolean dispatchTouchEvent(MotionEvent ev) {
//驗證事件是否連續(xù)
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
//這個變量用于記錄事件是否被處理完
boolean handled = false;
//過濾掉一些不合法的事件:當前的View的窗口被遮擋了。
if (onFilterTouchEventForSecurity(ev)) {
//如果事件發(fā)生的View在的窗口,沒有被遮擋
final int action = ev.getAction();
//重置前面為0 空盼,只留下后八位篱瞎,用于判斷相等時候校哎,可以提高性能。
final int actionMasked = action & MotionEvent.ACTION_MASK;
//判斷是不是Down事件局冰,如果是的話,就要做初始化操作
if (actionMasked == MotionEvent.ACTION_DOWN) {
//如果是down事件翁锡,就要清空掉之前的狀態(tài),比如,重置手勢判斷什么的邪财。
//比如,之前正在判斷是不是一個單點的滑動皂岔,但是第二個down來了速兔,就表示镀迂,不可能是單點的滑動,要重新開始判斷觸摸的手勢
//清空掉mFirstTouchTarget
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//檢查是否攔截事件
final boolean intercepted;
//如果當前是Down事件,或者已經(jīng)有處理Touch事件的目標了
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//判斷允不允許這個View攔截
//使用與運算作為判斷病瞳,可以讓我們在flag中嚎于,存儲好幾個標志
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//如果說允許攔截事件
if (!disallowIntercept) {
//確定是不是攔截了
intercepted = onInterceptTouchEvent(ev);
//重新恢復(fù)Action嫌吠,以免action在上面的步驟被人為地改變了
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
//如果說档痪,事件已經(jīng)初始化過了锯仪,并且沒有子View被分配處理斥扛,那么就說明,這個ViewGroup已經(jīng)攔截了這個事件
intercepted = true;
}
// Check for cancelation.
//如果viewFlag被設(shè)置了PFLAG_CANCEL_NEXT_UP_EVENT ,那么就表示衬鱼,下一步應(yīng)該是Cancel事件
//或者如果當前的Action為取消霉颠,那么當前事件應(yīng)該就是取消了。
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
//如果需要(不是取消瞳筏,也沒有被攔截)的話,那么在觸摸down事件的時候更新觸摸目標列表
//split代表苍柏,當前的ViewGroup是不是支持分割MotionEvent到不同的View當中
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
//新的觸摸對象,
TouchTarget newTouchTarget = null;
//是否把事件分配給了新的觸摸
boolean alreadyDispatchedToNewTouchTarget = false;
//事件不是取消事件,也沒有攔截那么就要判斷
if (!canceled && !intercepted) {
//如果是個全新的Down事件
//或者是有新的觸摸點
//或者是光標來回移動事件(不太明白什么時候發(fā)生)
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
//這個事件的索引,也就是第幾個事件桐罕,如果是down事件就是0
final int actionIndex = ev.getActionIndex(); // always 0 for down
//獲取分配的ID的bit數(shù)量
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
//清理之前觸摸這個指針標識,以防他們的目標變得不同步塘淑。
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
//如果新的觸摸對象為null(這個不是鐵定的嗎)并且當前ViewGroup有子元素
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
//下面所做的工作,就是找到可以接收這個事件的子元素
final View[] children = mChildren;
//是否使用自定義的順序來添加控件
final boolean customOrder = isChildrenDrawingOrderEnabled();
for (int i = childrenCount - 1; i >= 0; i--) {
//如果是用了自定義的順序來添加控件,那么繪制的View的順序和mChildren的順序是不一樣的
//所以要根據(jù)getChildDrawingOrder取出真正的繪制的View
//自定義的繪制,可能第一個會畫到第三個踏施,和第四個定铜,第二個畫到第一個曹动,這樣里面的內(nèi)容和Children是不一樣的
final int childIndex = customOrder ?
getChildDrawingOrder(childrenCount, i) : i;
final View child = children[childIndex];
//如果child不可以接收這個觸摸的事件利花,或者觸摸事件發(fā)生的位置不在這個View的范圍內(nèi)
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
//獲取新的觸摸對象,如果當前的子View在之前的觸摸目標的列表當中就返回touchTarget
//子View不在之前的觸摸目標列表那么就返回null
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
//如果新的觸摸目標對象不為空橄仍,那么就把這個觸摸的ID賦予它宪哩,這樣子品抽,
//這個觸摸的目標對象的id就含有了好幾個pointer的ID了
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
//如果子View不在之前的觸摸目標列表中,先重置childView的標志,去除掉CACEL的標志
resetCancelNextUpFlag(child);
//調(diào)用子View的dispatchTouchEvent,并且把pointer的id 賦予進去
//如果說嚼鹉,子View接收并且處理了這個事件赐稽,那么就更新上一次觸摸事件的信息,
//并且為創(chuàng)建一個新的觸摸目標對象浑侥,并且綁定這個子View和Pointer的ID
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
mLastTouchDownIndex = childIndex;
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//這里給mFirstTouchTarget賦值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
//如果newTouchTarget為null姊舵,就代表,這個事件沒有找到子View去處理它寓落,
//那么括丁,如果之前已經(jīng)有了觸摸對象(比如,我點了一張圖零如,另一個手指在外面圖的外面點下去)
//那么就把這個之前那個觸摸目標定為第一個觸摸對象躏将,并且把這個觸摸(pointer)分配給最近添加的觸摸目標
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// Dispatch to touch targets.
//如果沒有觸摸目標
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
//那么就表示我們要自己在這個ViewGroup處理這個觸摸事件了
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
//遍歷TouchTargt樹.分發(fā)事件,如果我們已經(jīng)分發(fā)給了新的TouchTarget那么我們就不再分發(fā)給newTouchTarget
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
//是否讓child取消處理事件考蕾,如果為true,就會分發(fā)給child一個ACTION_CANCEL事件
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//派發(fā)事件
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
//cancelChild也就是說会宪,派發(fā)給了當前child一個ACTION_CANCEL事件肖卧,
//那么就移除這個child
if (cancelChild) {
//沒有父節(jié)點,也就是當前是第一個TouchTarget
//那么就把頭去掉
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
//把下一個賦予父節(jié)點的上一個掸鹅,這樣當前節(jié)點就被丟棄了
predecessor.next = next;
}
//回收內(nèi)存
target.recycle();
//把下一個賦予現(xiàn)在
target = next;
//下面的兩行不執(zhí)行了塞帐,因為我們已經(jīng)做了鏈表的操作了。
//主要是我們不能執(zhí)行predecessor=target巍沙,因為刪除本節(jié)點的話葵姥,父節(jié)點還是父節(jié)點
continue;
}
}
//如果沒有刪除本節(jié)點,那么下一輪父節(jié)點就是當前節(jié)點句携,下一個節(jié)點也是下一輪的當前節(jié)點
predecessor = target;
target = next;
}
}
// Update list of touch targets for pointer up or cancel, if needed.
//遇到了取消事件榔幸、或者是單點觸摸下情況下手指離開,我們就要更新觸摸的狀態(tài)
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
//如果是多點觸摸下的手指抬起事件矮嫉,就要根據(jù)idBit從TouchTarget中移除掉對應(yīng)的Pointer(觸摸點)
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
整體流程:
DOWN事件下削咆,通過輪詢當前ViewGroup的下層View
同步進行判斷,是否在這個View的矩形范圍內(nèi)
如果在的話蠢笋,那么選中這個View進行dispatch轉(zhuǎn)發(fā)拨齐,他是ViewGroup會繼續(xù)轉(zhuǎn)發(fā)下層,如果是View直接消費處理昨寞!
MOVE類型瞻惋,因為其特性的原因厦滤,量大,用輪詢不合適
所以歼狼,在DOWN下來的時候直接記錄當前這個View馁害,然后默認后續(xù)直接轉(zhuǎn)發(fā)這個View
如果手指滑出當前View,那么事件信息傳遞過來的ActionID會改變蹂匹,改變ID后碘菜,進行將target變更!UP\ CANCEL會重置
3限寞、 View
源碼分析
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
* 將屏幕的按壓事件傳遞給目標view忍啸,或者當前view即目標view
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
//最前面這一段就是判斷當前事件是否能獲得焦點,
// 如果不能獲得焦點或者不存在一個View履植,那我們就直接返回False跳出循環(huán)
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
//設(shè)置返回的默認值
boolean result = false;
//這段是系統(tǒng)調(diào)試方面计雌,可以直接忽略
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
//Android用一個32位的整型值表示一次TouchEvent事件,低8位表示touch事件的具體動作,比如按下玫霎,抬起凿滤,滑動,還有多點觸控時的按下庶近,抬起翁脆,這個和單點是區(qū)分開的,下面看具體的方法:
//1 getAction:觸摸動作的原始32位信息鼻种,包括事件的動作反番,觸控點信息
//2 getActionMasked:觸摸的動作,按下,抬起叉钥,滑動罢缸,多點按下,多點抬起
//3 getActionIndex:觸控點信息
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
//當我們手指按到View上時,其他的依賴滑動都要先停下
// Defensive cleanup for new gesture
stopNestedScroll();
}
//過濾掉一些不合法的事件,比如當前的View的窗口被遮擋了投队。
if (onFilterTouchEventForSecurity(event)) {
//ListenerInfo 是view的一個內(nèi)部類 里面有各種各樣的listener,
// 例如OnClickListener枫疆,OnLongClickListener,OnTouchListener等等
ListenerInfo li = mListenerInfo;
//首先判斷如果監(jiān)聽li對象!=null 且我們通過setOnTouchListener設(shè)置了監(jiān)聽敷鸦,
// 即是否有實現(xiàn)OnTouchListener息楔,
// 如果有實現(xiàn)就判斷當前的view狀態(tài)是不是ENABLED,
// 如果實現(xiàn)的OnTouchListener的onTouch中返回true,并處理事件轧膘,則
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
//如果滿足這些條件那么返回true钞螟,這個事件就在此處理意味著這個View需要事件分發(fā)
result = true;
}
//如果上一段判斷的條件沒有滿足(沒有在代碼里面setOnTouchListener的話),
// 就判斷View自身的onTouchEvent方法有沒有處理谎碍,沒有處理最后返回false鳞滨,處理了返回true;
//也就是前面的判斷優(yōu)先級更高
if (!result && onTouchEvent(event)) {
result = true;
}
}
//系統(tǒng)調(diào)試分析相關(guān)蟆淀,沒有影響
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
//如果這是手勢的結(jié)尾拯啦,則在嵌套滾動后清理
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
先調(diào)用listener的onTouch澡匪,而onTouchEvent的調(diào)用是根據(jù)onTouch結(jié)果走,
onTouch的使用優(yōu)先級高于onTouchEvent褒链,onTouch通過返回值可以控制onTouchEvent的調(diào)用唁情。
事件消費過程
U型模型,事件從上到下傳遞甫匹,從下到上消費(無消費)
L型模型甸鸟,事件從上到下傳遞,后被攔截(有消費)
總結(jié)
事件分發(fā)就是事件從linux層通過驅(qū)動采集數(shù)據(jù)兵迅,底層使用epoll和inotify傳遞出來抢韭,F(xiàn)rameWork層通過InputReaderThread讀取,通過InputDispatcherThread分發(fā)到WMS恍箭,WMS將事件傳遞到Activity刻恭,上層的Activity、ViewGroup扯夭、View之間事件的分發(fā)和消費鳍贾。
具體流程:
1、事件信號是物理文件存儲數(shù)據(jù)交洗,位置:dev/input
2骑科、linux有提供相關(guān)的文件監(jiān)控api,其中使用了inotify(能監(jiān)控文件變化產(chǎn)生FD) 和epoll(可以監(jiān)控FD藕筋,以此配合完成文件的監(jiān)控與監(jiān)聽)
3纵散、Android寫了兩個線程來處理dev/input下面的信號(InputReaderThread和InputDispathcerThread)
4、專門寫了一個EventHub對象隐圾,里面用inotify+epoll對dev/input下進行監(jiān)控!
5掰茶、將該對象放到InputReaderThread當中去執(zhí)行暇藏,輪訓(xùn)getEvent(),這個里面有epoll_wait,相當于wait-notify機制濒蒋,喚醒的觸發(fā)點是/dev/input下的文件被改變盐碱,這個文件由驅(qū)動去推送數(shù)據(jù)
6、InputReaderThread當中將/dev/input下的數(shù)據(jù)提取沪伙,封裝瓮顽,然后交給InputDispathcerThread
7、InputDispathcerThread給最終選擇到對應(yīng)的ViewRootImpl(Window)
8围橡、中間的通信機制通過socketpair進行暖混,兩邊一人一組socketpair
9、然后在ViewRootImpl中對于Channel的連接的文件進行監(jiān)控翁授,一次導(dǎo)致能夠最終上層接受到touch信號拣播!