當前位置:首頁 » 安卓系統 » android的事件分發

android的事件分發

發布時間: 2024-04-02 18:26:20

『壹』 Android Touch事件分發處理機制詳解

Android應用的開發過程不可能不涉及到Touch事件的處理,簡單地如設置OnClickListener、OnLongClickListener等監聽器處理View的點擊事件,復雜地如在自定義View中通過重寫onTouchEvent來捕獲用戶交互事件以定製出各種效果,在使用的過程中或多或少會遇到一些奇怪的Bug,讓你對Touch事件「從哪來,到哪去」產生迷之疑惑,經過多少次徘徊之後終於決定系統的分析下源碼,本文就給大家分享下我的收獲。

MotionEvent作為Touch事件的載體,採用時間片來管理Touch事件所有相關行為的數據,本文這樣理解時間片這個概念:

通常MotionEvent會將觸發當前事件的Pointer作為主要Pointer,其PointerIndex為0,而MotionEvent通過提供getX()這類不帶index參數的介面以更方便的操作主要Pointer的數據。
了解了MotionEvent的組成結構之後,接下來就可以分析MotionEvent包含的事件類型了,MotionEvent通過getAction介面來獲取事件Action,而Action中低8位地址存儲的是事件類型(對於觸摸事件來說,主要包括Down、Move、Up、Cancel、PointerDown、PointerUp),高8位地址存儲的是PointerId(當事件類型為PointerDown、PointerUp時)。通常來說事件會以Down開始,以Up或Cancel結束,各事件所承擔的角色以及各自的特點在分析事件分發與處理的過程時再詳細說明。
另外,MotionEvent中的Flag需要說明一下:

本文僅分析Touch事件在Framework中Java層的傳遞,因此從事件傳遞到Activity開始分析。當Touch事件傳遞給Activity時,會調用Activity.dispatchTouchEvent(MotionEvent),Activity會將事件傳遞給其Window進行處理,實際會調用PhoneWindow.superDispatchTouchEvent(MotionEvent),PhoneWindow會將該事件傳遞給Android中View層級前埋中的頂層View(即DecorView)進行處理:

在Window未設置Callback的情況下,會調用父類的dispatchTouchEvent,DecorView繼承自FrameLayout,然後FrameLayout並未實現dispatchEvent,因此最終調用ViewGroup.dispatchTouchEvent,也就是Touch事件分發的核心邏輯所在,前文中提到MotionEvent中事件類型主要包括Down、Move、Up、Cancel、PointerDown、PointerUp,而dispatchTouchEvent根據事件的不同類型會做不同處理,因此這里分別進行分析:

Down事件處理

非異常情況下,Touch事件的事件周期總是以Down事件開始的,因此Down事件在整個事件分發邏輯中起關鍵作用,將決定了後續Move、Up及Cancel事件的處理主體,先看一張Down事件分發的流程圖:

從流程圖中可以看到,Down事件的分發邏輯主要目的在於尋找到能處理該Touch事件的View控制項(該View為以當前ViewGroup為Root節點的View層級中的View,利用尋找到的View創建事件處理Target),整個處理邏輯主要包含以下渣悔陸幾步:

Move、Up、Cancel事件處理

完成Down事件的分發邏輯後,就確定了該Down事件後續Move、Up及Cancel事件的處理主體(注意:這里並沒有確定PointerDown事件的處理主體,關於PointerDown事件的分發邏輯稍後分析),先通過一張流程圖來感受下Move、Up、Cancel事件的分發邏輯:

從流程圖可以看出,對於Move、Up、Cancel事件的分發步驟如如頃下:

PointerDown事件處理

PointerDown事件是在支持多Pointer(調用將FLAG_SPLIT_MOTION_EVENTS置位)的環境下,當有新的Pointer按下時產生的,該事件處理的特殊性在於會重新遍歷View層級,尋找可以處理新Pointer事件的Target,具體流程參考Down事件的分發邏輯;遍歷結束若仍沒有找到處理該事件的Target,則會將新Pointer的處理權設置給已有Target中最早被添加的Target。完成Target的尋找之後,會將該事件通過dispatchTransformedTouchEvent傳遞至所有已有Target進行處理,可以通過下面流程圖,對PointerDown事件的處理有一個更全局的認識:

PointerUp事件處理

相對於Up事件來說,對於PointerUp事件的處理區別在於當傳遞至所有已有Target結束之後並不能標記以Down事件起始的整個事件周期結束,僅能標記其關聯Pointer(以PointerDown事件起始)的事件周期結束,因此不會清除所有狀態,而僅會從已有Target中移除掉與該Pointer相關的部分。

onInterceptTouchEvent

在ViewGroup進行事件分發的過程中,會調用該函數來確定是否需要攔截事件,當該函數返回true時該事件將會被攔截,即不會進行正常的View層級傳遞,而是直接由該ViewGroup來處理,而攔截後的操作需要根據攔截事件的類型不同而不同:

dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits)

在將事件傳遞給Target進行處理之前會調用該函數對MotionEvent進行處理:

MotionEvent.split(int idBits)

判斷一個View控制項是否消費一個事件,是由View.dispatchEvent的返回值來決定的,而View.dispatchEvent用於尋找事件的最終消費者,話不多說,還是通過一張流程圖來個直觀感受:

從流程圖中可以看出,View會根據ouch事件對Scroll狀態進行調整,並尋找該事件的最終處理器:

View.dispatchEvent將向其直接ViewGroup返回是否消費掉該事件,返回值將決定上級ViewGroup是否需要繼續詢問其他子View是否需要消費該事件。這就是View中分發事件的邏輯,真是簡單粗暴!

從View.dispatchEvent的分析中可以發現當未對View設置mTouchListener或mTouchListener未消費掉該事件時,Touch事件最終將由View.onTouchEvent來決定是否消費,自定義View可以重寫該方法實現自身的邏輯,此處僅分析View中的通用處理邏輯:

從上述分析可以很開心地發現熟悉的onClick及onLongClick事件的產生邏輯,若是之前沒看過類似的文章,應該會有原來如此的感覺吧,哈哈~~

至此,Touch事件的分發與處理流程算是走通了,個人看完整個源碼之後有種豁然開朗的感覺,能很清晰的分析向「為什麼事件有時候傳到某個View有時候卻不傳?」、「有時候只傳前面幾個事件後面卻不傳了?」等問題,也希望本文的分析能讓你更清晰地感知Android中Touch事件的傳遞流程,如果發現文中有何錯誤,希望不吝賜教!

『貳』 android事件分發機制是什麼設計模式

android事件分發機制 就是一個觸摸事件發生了,從一個窗口傳遞到一個視圖,再傳遞到另外一個視圖,最後被消費的過程,在android中還是比較復雜的傳遞流程如下:

(1) 事件從Activity.dispatchTouchEvent()開始傳遞,只要沒有被停止或攔截,從最上層的View(ViewGroup)開始一直往下(子View)傳遞。子View可以通過onTouchEvent()對事件進行處理。

(2) 事件由父View(ViewGroup)傳遞給子View,ViewGroup可以通過onInterceptTouchEvent()對事件做攔截,停止其往下傳遞。

(3) 如果事件從上往下傳遞過程中一直沒有被停止,且最底層子View沒有消費事件,事件會反嚮往上傳遞,這時父View(ViewGroup)可以進行消費,如果還是沒有被消費的話,最後會到Activity的onTouchEvent()函數。

(4) 如果View沒有對ACTION_DOWN進行消費,之後的其他事件不會傳遞過來。

(5) OnTouchListener優先於onTouchEvent()對事件進行消費。

上面的消費即表示相應函數返回值為true。

『叄』 誰可以解釋下,android事件分發為什麼要設計成從根view到子view,而不是從子vie

Android事件傳遞流程在網上可以找到很多資料,FrameWork層輸入事件和消費事件,可以參考:
Touch事件派發過程詳解
這篇blog闡述了底層是如何處理屏幕輸,並往上傳遞的。Touch事件傳遞到Activity的DecorView時,往下走就是ViewGroup和子View之間的事件傳遞,可以參考郭神的這兩篇博客
Android事件分發機制完全解析,帶你從源碼的角度徹底理解(上)
Android事件分發機制完全解析,帶你從源碼的角度徹底理解(下)
郭神的兩篇博客清楚明白地說明了View之間事件傳遞的大方向,但是具體的一些晦暗的細節闡述較少,本文主要是總結這兩篇博客的同時,側重於兩點:
事件分發過程中一些細節到底如何實現的?
子view到底如何和父View搶事件,父View又是如何攔截事件不發送給子View,以及如果我們需要處理這種混亂的關系才能讓兩者和諧相處?。
MotionEvent抽象
要明白View的事件傳遞,很有必要先說一下Touch事件是如何在Android系統中抽象的,這主要使用的就是MotionEvent。這個類經歷了幾次重大的修改,一次是在2.x版本支持多點觸摸,一次是4.x將大部分代碼甩給native層處理。
一次簡單的事件
我們先舉個栗子來說明一次完整的事件,用戶觸屏 滑動 到手機離開屏幕,這認為是一次完整動作序列(movement traces)。一個動作序列中包含很多動作Action,比如在用戶按下時,會封裝一個MotionEvent,分發給視圖樹,我們可以通過motionevent.getAction拿到這個動作是ACTION_DOWN。同樣,在手指抬起時,我們可以接收到Action類型是Action_UP的MotionEvent。對於滑動(MOVE)這個操作,Android為了從效率出發,會將多個MOVE動作打包到一個MotionEvent中。通過getX getY可以獲取當前的坐標,如果要訪問打包的緩存數據,可以通過getHistorical**()函數來獲取。
加入多點觸摸
對於單點的操作來看,MotionEvent顯得比較簡單,但是考慮引入多點觸摸呢?我們定義一個接觸點為(Pointer)。每一個觸摸點Pointer都會有一個當次動作序列的唯一Id和Index.MotionEvent中多個手指的操作API大部分都是通過pointerindex來進行的,如:獲取不同Pointer的觸碰位置,getX(int pointerIndex);獲取PointerId等等。大部分情況下,pointerid == pointeridex。
我們從onTouch接受到一個MotionEvent,怎麼拿到多個觸碰點的信息?為了解開筆者剛開始學習這部分知識時的困惑,我們首先樹立起一種概念:一個MotionEvent只允許有一個Action(動作),而且這個Action會包含觸發這次Action的觸碰點信息,對於MOVE操作來說,一定是當前所有觸碰點都在動。只有ACTION_POINTER_DOWN這類事件事件會在Action裡面指定是哪一個POINTER按下。
在MotionEvent的底層實現中,是通過一個16位來存儲Action和Pointer信息(PointerIndex)。低8位表示Action,理論上可以表示255種動作類型;高8位表示觸發這個Action的PointerIndex,理論上Android最多可以支持255點同時觸摸,但是在上層代碼使用的時候,默認多點最多存在32個,不然事件在分發的時候會有問題。
ACTION_DOWN OR ACTION_POINTER_DOWN:
這兩個按下操作的區別是ACTION_DOWN是一個系列動作的開始,而ACTION_POINTER_DOWN是在一個系列動作中間有另外一個觸碰點觸碰到屏幕。
這部分詳細的描述,請參考:
android觸控,先了解MotionEvent
到這里,鋪墊終於結束了,我們開始直奔主題。
View的事件傳遞
Android的Touch事件傳遞到Activity頂層的DecorView(一個FrameLayout)之後,會通過ViewGroup一層層往視圖樹的上面傳遞,最終將事件傳遞給實際接收的View。下面給出一些重要的方法。如果你對這個流程比較熟悉的話,可以跳過這里,直接進入第二部分。
dispatchTouchEvent
事件傳遞到一個ViewGroup上面時,會調用dispatchTouchEvent。代碼有刪減
public boolean dispatchTouchEvent(MotionEvent ev) {

boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;

// Attention 1 :在按下時候清除一些狀態
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
//注意這個方法
resetTouchState();
}

// Attention 2:檢查是否需要攔截
final boolean intercepted;
//如果剛剛按下 或者 已經有子View來處理
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// 不是一個動作序列的開始 同時也沒有子View來處理,直接攔截
intercepted = true;
}

//事件沒有取消 同時沒有被當前ViewGroup攔截,去找是否有子View接盤
if (!canceled && !intercepted) {
//如果這是一系列動作的開始 或者有一個新的Pointer按下 我們需要去找能夠處理這個Pointer的子View
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down

//上面說的觸碰點32的限制就是這里導致
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;

final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);

//對當前ViewGroup的所有子View進行排序,在上層的放在開始
final ArrayList<View> preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);

// canViewReceivePointerEvents visible的View都可以接受事件
// isTransformedTouchPointInView 計算是否落在點擊區域上
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}

//能夠處理這個Pointer的View是否已經處理之前的Pointer,那麼把
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.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
} }
//Attention 3 : 直接發給子View
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
= true;
break;
}

}
}

}
}

// 前面已經找到了接收事件的子View,如果為NULL,表示沒有子View來接手,當前ViewGroup需要來處理
if (mFirstTouchTarget == null) {
// ViewGroup處理
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {

if() {
//ignore some code
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
}

}
return handled;
}

上面代碼中的Attention在後面部分將會涉及,重點注意。
這里需要指出一點的是,一系列動作中的不同Pointer可以分配給不同的View去響應。ViewGroup會維護一個PointerId和處理View的列表TouchTarget,一個TouchTarget代表一個可以處理Pointer的子View,當然一個View可以處理多個Pointer,比如兩根手指都在某一個子View區域。TouchTarget內部使用一個int來存儲它能處理的PointerId,一個int32位,這也就是上層為啥最多隻能允許同時最多32點觸碰。
看一下Attention 3 處的代碼,我們經常說view的dispatchTouchEvent如果返回false,那麼它就不能系列動作後面的動作,這是為啥呢?因為Attention 3處如果返回false,那麼它不會被記錄到TouchTarget中,ViewGroup認為你沒有能力處理這個事件。
這里可以看到,ViewGroup真正處理事件是在dispatchTransformedTouchEvent裡面,跟進去看看:
dispatchTransformedTouchEvent
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {

//沒有子類處理,那麼交給viewgroup處理
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}

handled = child.dispatchTouchEvent(transformedEvent);
}
return handled;
}

可以看到這里不管怎麼樣,都會調用View的dispatchTouchEvent,這是真正處理這一次點擊事件的地方。
dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
if (onFilterTouchEventForSecurity(event)) {
//先走View的onTouch事件,如果onTouch返回True
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}

if (!result && onTouchEvent(event)) {
result = true;
}
}
return result;
}

我們給View設置的onTouch事件處在一個較高的優先順序,如果onTouch執行返回true,那麼就不會去走view的onTouchEvent,而我們一些點擊事件都是在onTouchEvent中處理的,這也是為什麼onTouch中返回true,view的點擊相關事件不會被處理。

『肆』 餓了么開源的Android跨進程事件分發框架HermesEventBus

原理

事件收發是基於EventBus,IPC通信是基於Hermes。Hermes是一個簡單易用的Android IPC庫。

首先選一個進程作為主進程,將其他進程作為子進程。

每次一個event被發送都會經過以下四步:

1、使用Hermes庫將event傳遞給主進程。

2、主進程使用EventBus在主進程內部發送event。

3、主進程使用Hermes庫將event傳遞給所有的子進程。

4、每個子進程使用EventBus在子進程內部發送event。

用法

能在app內實現多進程event收發,也可以跨app實現event收發。

單一app內的用法

如果你在單一app內進行多進程開發,那麼只需要做以下三步:

Step 1

在gradle文件中加入下面的依賴:
dependencies {
compile 'xiaofei.library:hermes-eventbus:0.1.1'
}

Step 2

在Application的onCreate中加上以下語句進行初始化:
HermesEventBus.getDefault().init(this);

Step 3

每次使用EventBus的時候,用HermesEventBus代替EventBus。
HermesEventBus.getDefault().register(this);
HermesEventBus.getDefault().post(new Event());

HermesEventBus也能夠在一個進程間傳遞event,所以如果你已經使用了HermesEventBus,那麼就不要再使用EventBus了。

多個app間的用法(使用DroidPlugin的時候就是這種情況)

如果你想在多個app間收發event,那麼就做如下幾步:

Step 1

在每個app的gradle文件中加入依賴:
dependencies {
compile 'xiaofei.library:hermes-eventbus:0.1.1'
}

Step 2

選擇一個app作為主app。你可以選擇任意app作為主app,但最好選擇那個存活時間最長的app。

在使用DroidPlugin的時候,你可以把宿主app作為主app。

在主app的AndroidManifest.xml中加入下面的service:
<service android:name="xiaofei.library.hermes.HermesService$HermesService0"/>

你可以加上一些屬性。

Step 3

在app間收發的事件類必須有相同的包名、相同的類名和相同的方法。

務必記住在代碼混淆的時候將這些類keep!!!

Step 4

在主app的application類的onCreate方法中加入:
HermesEventBus.getDefault().init(this);

在其他app的Application類的onCreate方法中加入:
HermesEventBus.getDefault().connectApp(this, packageName);

「packageName」指的是主app的包名。

Step 5

每次使用EventBus的時候,用HermesEventBus代替EventBus。
HermesEventBus.getDefault().register(this);
HermesEventBus.getDefault().post(new Event());

HermesEventBus也能夠在一個進程間傳遞event,所以如果你已經使用了HermesEventBus,那麼就不要再使用EventBus了。

『伍』 android怎麼主動分發事件

Android中,所有的操作類型事件都由如下三個部分作為基礎:


按下(ACTION_DOWN)

移動(ACTION_MOVE)

抬起(ACTION_UP)

這三部分都寄生於onTouch事件中,由MontionEvent類中定義的三個常量進行區分。


Android中與Touch事件相關的方法為:

removeTapCallback();
removeLongPressCallback();
break;

case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();

// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();

setPressed(false);
}
}
break;
}
return true;
}

return false;
}

『陸』 Android事件分發機制

Android中對視圖的Touch事件進行分發處理。
單手指操作:ACTION_DOWN -> ACTION_MOVE -> ACTION_UP
多手指操作:ACTION_DOWN -> ACTION_POINTER_DOWN -> ACTION_MOVE -> ACTION_POINTER_UP -> ACTION_UP.

(1) dispatchTouchEvent() :事件分發

(2) onInterceptTouchEvent() :事件攔截

(3) onTouchEvent() :事件處理

ViewGroup 的相關事件有三個:onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent。

View 的相關事件只有兩個:dispatchTouchEvent、onTouchEvent。

先分析ViewGroup的處理流程:首先得有個結構模型概念:ViewGroup和View組成了一棵樹形結構,最頂層為Activity的ViewGroup,下面有若乾的ViewGroup節點,每個節點之下又有若乾的ViewGroup節點或者View節點,依次類推。如圖:

點擊事件達到頂級 View(一般是一個 ViewGroup),會調用 ViewGroup 的 dispatchTouchEvent 方法,如果頂級 ViewGroup 攔截事件即 onInterceptTouchEvent 返回 true,則事件由 ViewGroup 處理,這時如果 ViewGroup 的 mOnTouchListener 被設置,則 onTouch 會被調用,否則 onTouchEvent 會被調用。也就是說如果都提供的話,onTouch 會屏蔽掉 onTouchEvent。在 onTouchEvent 中,如果設置了 mOnClickListenser,則 onClick 會被調用。如果頂級 ViewGroup 不攔截事件,則事件會傳遞給它所在的點擊事件鏈上的子 View,這時子 View 的 dispatchTouchEvent 會被調用。如此循環。

『柒』 Android View 事件分發機制

Android 事件機制包含系統啟動流程、輸入管理(InputManager)、系統服務和 UI 的通信(WindowManagerService + ViewRootImpl + Window)、事件分發等一系列的環節。

Android 系統中將輸入事件定義為 InputEvent,根據輸入事件的類型又分為了 KeyEvent(鍵盤事件) 和 MotionEvent(屏幕觸摸事件)。這些事件統一由系統輸入管理器 InputManager 進行分發。

在系統啟動的時候,SystemServer 會啟動 WindowManagerService,WMS 在啟動的時候通過 InputManager 來負責監控鍵盤消息。

InputManager 負責從硬體接收輸入事件,並將事件通過 ViewRootImpl 分發給當前激活的窗口處理,進而分發給 View。

Window 和 InputManagerService 之間通過 InputChannel 來通信,底層通過 socket 進行通信。

Android Touch 事件的基礎知識:

KeyEvent 對應了鍵盤的輸入事件;MotionEvent 就是手勢事件,滑鼠、筆、手指、軌跡球等相關輸入設備的事件都屬於 MotionEvent。

InputEvent 統一由 InputManager 進行分發,負責與硬體通信並接收輸入事件。

system_server 進程啟動時會創建 InputManagerService 服務。

system_server 進程啟動時同時會啟動 WMS,WMS 在啟動的時候就會通過 IMS 啟動 InputManager 來監控鍵盤消息。

App 端與服務端建立了雙向通信之後,InputManager 就能夠將產生的輸入事件從底層硬體分發過來,Android 提供了 InputEventReceiver 類,以接收分發這些消息:

Window 和 IMS 之間通過 InputChannel 通信。InputChannel 是一個 pipe,底層通過 socket 進行通信。在 ViewRootImpl.setView() 過程中注冊 InputChannel。

Android 事件傳遞機制是 先分發再處理 ,先由外部的 View 接收,然後依次傳遞給其內層的 View,再從最內層 View 反向依次向外層傳遞。

三個方法的關系如下:

分發事件:

應用了樹的 深度優先搜索演算法 (Depth-First-Search,簡稱 DFS 演算法),每個 ViewGroup 都持有一個 mFirstTouchTarget, 當接收到 ACTION_DOWN 時,通過遞歸遍歷找到 View 樹中真正對事件進行消費的 Child,並保存在 mFirstTouchTarget 屬性中,依此類推組成一個完整的分發鏈。在這之後,當接收到同一事件序列的其它事件如 ACTION_MOVE、ACTION_UP 時,則會跳過遞歸流程,將事件直接分發給下一級的 Child。

ViewGroup 分發事件的主要的任務是找一個 Target,並且用這個 Target 處理事件,主要邏輯如下 :

為什麼倒序查找 TouchTarget?
如果按添加順序遍歷,當 View 重疊時(FrameLayout),先添加的 View 總是能消費事件,而後添加的 View 不可能獲取到事件。

攔截事件:

[1] Android 事件分發機制的設計與實現
[2] Android 事件攔截機制的設計與實現

熱點內容
存儲過程的應用場景 發布:2024-05-07 15:12:16 瀏覽:611
車內配置怎麼看 發布:2024-05-07 15:11:39 瀏覽:207
outlook已發送文件夾 發布:2024-05-07 14:08:13 瀏覽:31
佛系源碼 發布:2024-05-07 14:04:03 瀏覽:674
php螞蟻 發布:2024-05-07 13:49:22 瀏覽:401
phpfpmpid 發布:2024-05-07 13:44:29 瀏覽:521
linuxtty1 發布:2024-05-07 13:40:10 瀏覽:865
linuxshell腳本中if 發布:2024-05-07 13:25:01 瀏覽:221
phpmysql擴展 發布:2024-05-07 13:25:01 瀏覽:800
星密碼開網店怎麼樣 發布:2024-05-07 13:23:26 瀏覽:354