click事件生成和attachInfo傳遞
click事件生成(onClick呼叫)
在Android開發中經常用到click事件監聽,但其實click事件並不是地方傳上來的事件,底層上報事件都是touch事件,而click事件其實是根據touch事件生成的,或者說click事件並不是一個純粹的事件
在View的onTouchEvent方法中,在收到MotionEvent.ACTION_DOWN事件的時候有如下邏輯
這裡擷取了部分主要原始碼,這裡其實主要設定了下flag,然後主要是呼叫了checkForLongClick(走if分支時CheckForTap也會呼叫checkForLongClick方法)該方法主要是傳送一個延時事件,如果到時該延時事件沒有取消,則會執行該事件,這就是LongClick事件的生成,稍後再總結長按事件
在收到MotionEvent.ACTION_UP事件的時候
這裡擷取了部分主要原始碼,前面removeLongPressCallback方法會移除在down事件時傳送的長按事件的延時任務,而mPerformClick任務則是執行click事件
click事件
根據前面的分析,click事件主要程式碼如上,其呼叫有兩種,首先post一個PerformClick任務,如果post返回false則呼叫performClickInternal方法,其最後的邏輯都是一致的,這裡先看下PerformClick任務
顯然PerformClick任務中就是呼叫performClickInternal方法
繼續走讀程式碼,performClickInternal主要時呼叫performClick方法
到performClick方法就很明朗了,這裡有獲取onClick監聽,如果有新增onClick監聽器,則這裡呼叫其onClick方法
長按事件
長按事件和click事件相似,不過長按事件實際是在down時生成的(延時任務),不過如果up時還沒執行則會取消長按事件(還有些如cancel事件等場景這裡不具體討論,只是大概介紹下流程)
根據前面介紹的情況,這裡走讀下一般場景下呼叫,如上,走checkForLongClick方法
顯然這裡是postDelayed一個CheckForLongPress任務,檢視其run方法:
顯然,CheckForLongPress任務觸發時會呼叫performLongClick方法,繼續走讀程式碼
到這裡就到到具體事件回撥的地方了,在performLongClickInternal方法中,如果有設定長按事件監聽器,這裡即會呼叫其onLongClick方法
post和postDelayed
前面分析中和View程式碼中有多處post和postDelayed方法呼叫,但其並不是直接有個handle去呼叫,View自己有實現post和postDelayed方法
如上,其post和postDelayed方法結構相似,其都是呼叫的mAttachInfo的mHandler物件的post和postDelayed方法(getRunQueue的相關呼叫看程式碼只在dispatchAttachedToWindow方法中會執行,正常來說一般在介面顯示的時候就會走dispatchAttachedToWindow方法,後面除非移除控制元件再次新增,不然基本不會 重複呼叫,所以getRunQueue的相關呼叫一般情況下基本不會走到)
mAttachInfo在下面會具體分析,其mHandler是ViewRootImpl中的ViewRootHandler內部類的一個物件(ViewRootHandler繼承Handler)
attachInfo傳遞
View的mAttachInfo最開始是在ViewRootImpl中建立的,然後在第一次繪製的時候分發到控制元件樹的根節點,然後依次向子節點分發
如上,在ViewRootImpl的構造方法裡建立了View.AttachInfo的物件mAttachInfo
然後在ViewRootImpl的performTraversals方法中mFirst條件程式碼中有呼叫host.dispatchAttachedToWindow方法,如下
這裡host即是一般控制元件樹的根節點
然後看下View和ViewGroup的dispatchAttachedToWindow方法邏輯
在View的dispatchAttachedToWindow中首先會給當前控制元件的mAttachInfo變數賦值,然後有些回撥等,如onAttachedToWindow呼叫
在ViewGroup的dispatchAttachedToWindow主要是將attachInfo傳遞到控制元件樹的子節點
這裡有super呼叫,即會走View的dispatchAttachedToWindow方法,這樣attachInfo就賦值給了當前控制元件
然後有對mChildren和mTransientViews分別進行遍歷呼叫,前者是一般的控制元件樹傳遞,後者是臨時控制元件的傳遞,其邏輯相似(臨時控制元件用的場景比較少,而且這裡邏輯相似不單獨分析)
這裡mChildren就是控制元件樹中的當前控制元件的子節點控制元件陣列,這裡對子節點控制元件分別呼叫dispatchAttachedToWindow方法,即會將attachInfo傳遞給了其子控制元件,就這樣,attachInfo就從控制元件樹的根節點一級一級的傳遞到控制元件樹所有控制元件
不要在onAttachedToWindow中呼叫bringToFront方法
之前工作有遇到個控制元件點選無響應的問題,最後分析是控制元件的mAttachInfo未賦值,而其根因是在其無響應控制元件同級控制元件的onAttachedToWindow中有呼叫bringToFront方法,在atttachInfo分發時改變了控制元件的順序,導致有的控制元件並未分發到,從而導致了問題
這裡簡單介紹下
在前面attachInfo分發中(ViewGroup. dispatchAttachedToWindow)已經瞭解到其是遍歷mChildren呼叫其子元素的dispatchAttachedToWindow方法
然後我們先看下bringToFront方法(View),其會呼叫其父節點控制元件的bringChildToFront方法
其會呼叫其父節點控制元件的bringChildToFront方法(ViewGroup)
bringChildToFront方法前面主要就是操作mChildren陣列,將對應的控制元件先從mChildren移除,然後新增到mChildren末尾
然後回到這個問題的過程,
這裡畫了個簡易流程圖來說明,這裡假設假設父控制元件是當前分發attachInfo的控制元件,其有兩個子控制元件,子控制元件1在onAttachToWindow方法中呼叫了bringToFront方法
然後分析其分發過程:
- 將attachInfo賦值給父控制元件
- 遍歷其子控制元件,先呼叫其第一個子控制元件,這裡即子控制元件1的dispatchAttachedToWindow方法,然後會將attachInfo賦值給子控制元件1,然後回撥其onAttachedToWindow方法,然後其呼叫了bringToFront方法,即會將子控制元件1挪到其父控制元件的子控制元件列表的末尾,即變成右圖所示
- 繼續遍歷,這時呼叫父控制元件的第二個子控制元件,這裡根據上圖右圖所示仍是子控制元件1
從上面分發過程可知,這個過程中子控制元件2其實並沒有遍歷到,也就沒有被分發attahInfo,當然其也不會回撥onAttachedToWindow,可能還有些其他問題,不過其mAttachInfo未賦值,根據前面的分析,其點選事件也不會響應,不會回撥onClick方法