一、前言
有時候,我們會需要用到 View.post()
方法,來將一個 Runnable 發送到主線程去執行。這一切,看似很美好,它最終會通過一個 Handler.post()
方法去執行,又避免我們重新定義一個 Handler 對象。
但是,從 Android 7.0(Api level 24) 開始,View.post() 將不再那么靠譜了,你 post() 出去的 Runnable ,可能永遠也不會有機會執行到。
二、post 在 7.0 的差異
2.1 post 方法的差異
前面提到,這個問題只出現在 Android 7.0 上。那么就先從源碼分析 Android 7.0 到底對 View.post() 做了什么改動。
用 Diff 看一下它們的差異,左邊是 Api Level 24+(以下簡稱 Api24) 的代碼,右邊是 Api level 23-(以下簡稱 Api23) 的代碼。
很明顯的可以看出來,它們只有在 mAttachInfo
為 null 的時候,執行的邏輯才會有差異。
Api24 中,會調用 getRunQueue().post(action)
,而 Api23 會調用 ViewRootImpl.getRunQueue().post(action)
方法,他們的差異就在這里。
2.2 Api23 post 的細節
先簡單理解一下,ViewRootImpl
是什么。
ViewRootImpl
可以理解是一個 Activity 的 ViewTree 的根節點的實例。每個 ViewRootImpl 就是用來管理 DecorView 和 ViewTree。
ViewRootImpl
中的用來承載 Runnable 的隊列是 sRunQueues ,它一個靜態的變量,也就是說在 App 的生命周期內,ViewRootImpl
中的這個消息隊列都是同一個。
再來看看前面提到的 ViewRootImpl.getRunQueue().post()
到底干了什么?
post()
方法只是單純的將它包裝成一個 HandlerAction 對象,然后放入 mActions 這個 ArrayList 中。繼續追查下去就需要知道 mActions 中添加的 HandlerAction 在何時被消費掉了。
消費 HandlerAction 的地方,是 executeActions()
方法。
它最終,還是調用的 handler.postDelayed()
,這沒什么好說的,關鍵點在于 executeAction()
方法,是在什么時候被調用的。
executeAction()
是被 TraversalRunnable 調用 doTraversa()
,在doTraversa()
方法中,進行調用的。而 TraversalRunnable 又是通過 Choreographer.postCallBack()
去循環調用的。這個 Choreographer
通過 doScheduleCallback()
發送一個 MSG_DO_SCHEDULE_CALLBACK
類型的消息循環調用,間隔就是一個 VSync 的間隔。
關于 Choreographer ,不是本文的重點,有興趣可以單獨了解一下。
所以,在 Api23 以下,executeAction()
是會被循環調用,基本上其內的 mActions 只要有未執行的 Runnable 立刻就會被消費掉。
所以在 Api23 以下的設備上,View.post() 基本上是靠譜的,post 出去的 Runnable 都會有機會執行到。
2.3 Api24 的細節
再來看看在 Api24 中的實現細節,在 Api24 中,調用的是 getRunQueue().post()
方法,它操作的是一個 HandlerActionQueue
對象。
內部的結構其實和 Api23 很像,也是維護了一個 HandlerAction 的數組 mActions 。
最終消費掉 mActions 的地方,依然是一個 executeActions()
方法。
回到根本的問題,executeActions()
方法在什么時機會被調用到,繼續追查可以看到它在 View.dispatchAttachedToWindow()
方法中,會被調用。
既然,executeActions()
方法,在 Api24 及以上,只會在 dispatchAttachedToWindow()
的方法中,才有機會被調用到,而 View.dispatchAttachedToWindow()
方法,只有在這個 View 通過 addView()
等方法,加入到一個 ViewGroup 的時候,才會被調用到。這就導致寫在 Layout 布局中的控件,是不會有機會再調用 addView()
方法的,所以它永遠也得不到執行。這也就到時了 Api24 下,View.post()
表現的現象不一致的緣故。
三、小結
View.post()
方法,在不同版本的差異,根本原因還是在于 Api23 和 Api24 中,executeActions()
方法的調用時機不同,導致 View 在沒有 mAttachInfo 對象的時候,表現不一樣了。
所以我們在使用的過程中需要慎用,區分出實際使用的場景,一般規范自己的代碼即可:
在 View 已經被顯示出來之后,再調用 View.post()
方法(這個時候 mAttachInfo 已經不為空了)。
盡量避免使用 View.post()
方法,可以直接使用 Handler.post()
方法來替代。
總結
以上所述是小編給大家介紹的View.post() 不靠譜的地方,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對腳本之家網站的支持!
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com