版權聲明:api
本帳號發佈文章均來自公衆號,承香墨影(cxmyDev),版權歸承香墨影全部。數組
每週會統一更新到這裏,若是喜歡,可關注公衆號獲取最新文章。源碼分析
未經容許,不得轉載。佈局
這篇文章以前發過一遍,可是有讀者指出來有些地方描述的有問題,我後來再看的時候也以爲有問題,因此把以前的文章刪掉(主線是沒有問題的,刪掉只是是避免更多的人誤會),準備修改勘誤以後,再從新發布一遍,此次會補齊描述問題的 Demo 。post
有問題繼續文章後面留言,再次感謝細心的讀者指出文章內的錯誤。spa
有時候,咱們會須要用到 View.post()
方法,來將一個 Runnable 發送到主線程去執行。這一切,看似很美好,它最終會經過一個 Handler.post()
方法去執行,又避免咱們從新定義一個 Handler 對象。線程
可是,在 Android 7.0(Api level 24) 上,View.post()
將再也不那麼靠譜了,你 post()
出去的 Runnable ,可能永遠也不會有機會獲得執行。咱們先來看看它們的細節。3d
前面提到,這個問題只出如今 Android 7.0 上。那麼就先從源碼分析 Android 7.0 到底對 View.post()
作了什麼改動。code
用 Diff 看一下它們的差別,左邊是 Api Level 24(如下簡稱 Api24) 的代碼,右邊是 Api level 23-(如下簡稱 Api23) 的代碼。cdn
很明顯的能夠看出來,它們只有在 mAttachInfo 爲 null 的時候,執行的邏輯纔會有差別。
Api24 中,會調用 getRunQueue().post(action)
,而 Api23 會調用 ViewRootImpl.getRunQueue().post(action)
方法,他們的差別就在這裏。
先簡單理解一下,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 都會有機會執行到。
再來看看在 Api24 中的實現細節,在 Api24 中,調用的是 getRunQueue().post()
方法,它操做的是一個 HandlerActionQueue 對象。
內部的結構其實和 Api23 很像,也是維護了一個 HandlerAction 的數組 mActions 。
最終消費 mActions 的地方,依然是一個 executeActions()
方法。
回到根本的問題,executeActions()
方法在什麼時機會被調用到,繼續追查能夠看到它在 View.dispatchAttachedToWindow()
方法中,會被調用。
既然,executeActions()
方法,在 Api24 及以上,只會在 dispatchAttachedToWindow()
的方法中,纔有機會被調用到,而 View.dispatchAttachedToWindow()
方法,只有在這個 View 經過 addView()
方法,或者本來寫在頁面佈局的 xml 中(實際上也是調用的 addView()
),加入到一個 ViewGroup 的時候,纔會被調用到。
這就致使,若是你只是經過 new 或者使用 LayoutInflater 建立了一個 View ,而沒有將它經過 addView()
加入到 佈局視圖中去,你經過這個 View.post()
出去的 Runnable ,將永遠不會被執行到。 這也就是到了 Api24 下,View.post()
表現的現象不一致的緣故。
既然只是復現這個問題,秉承最小改動原則,構造一個最簡單的場景,單獨 new 一個 View 出來,而後經過它去調用 post()
方法,看看執行的結果。
能夠看到,這裏直接 new 了一個 View,而後 post 出去了一個 Runnable ,間隔 10s 以後,將這個 View 加入到根佈局中。
看看在 Api 23 下的執行效果:
能夠看到,在 Api 23一下,這裏是 Api19,新 new 出來的 View 對象,post 出去的 Runnable ,會當即獲得執行,不須要等待 addView()
的執行。
再來看看在 Api24 下的執行效果:
從執行時間上能夠看出來,post 出去的 Runnable ,並非當即被執行了,而是等到了 addView()
的調用以後,才被執行的,這個中間正好被間隔了 10s。
聽說這個問題,在 Android 8.0 上又被修改回去了,專門找了一款 8.0 的設備試試運行結果,以下圖:
25 是 Android 8.0 的預覽版,這裏能夠看到,依然是和在 7.0 上的表現同樣,會等到最終 addView()
的時候再執行,正式版不知道會不會有所改動,這個還有待驗證。
基本上肯定,受到影響的是 Android Api 24+,可是依然是開發者須要注意的,畢竟發佈出去的 App ,具體運行在什麼設備上,這就不是咱們能決定的了。
View.post()
方法,在不一樣版本的差別,根本緣由仍是在於 Api23 和 Api24 中,executeActions()
方法的調用時機不一樣,致使 View 在沒有 mAttachInfo 對象的時候,表現不同了。
因此咱們在使用的過程當中須要慎用,區分出實際使用的場景,通常規範本身的代碼便可: