自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

View.post() 不靠譜的地方你知道嗎?

開發(fā) 開發(fā)工具
View.post() 方法,在不同版本的差異,根本原因還是在于 Api23 和 Api24 中,executeActions() 方法的調(diào)用時(shí)機(jī)不同,導(dǎo)致 View 在沒有 mAttachInfo 對(duì)象的時(shí)候,表現(xiàn)不一樣了。

這篇文章之前發(fā)過一遍,但是有讀者指出來有些地方描述的有問題,我后來再看的時(shí)候也覺得有問題,所以把之前的文章刪掉(主線是沒有問題的,刪掉只是是避免更多的人誤會(huì)),準(zhǔn)備修改勘誤之后,再重新發(fā)布一遍,這次會(huì)補(bǔ)齊描述問題的 Demo 。

有問題繼續(xù)文章后面留言,再次感謝細(xì)心的讀者指出文章內(nèi)的錯(cuò)誤。

一、前言

有時(shí)候,我們會(huì)需要用到 View.post() 方法,來將一個(gè) Runnable 發(fā)送到主線程去執(zhí)行。這一切,看似很美好,它最終會(huì)通過一個(gè) Handler.post() 方法去執(zhí)行,又避免我們重新定義一個(gè) Handler 對(duì)象。

但是,在 Android 7.0(Api level 24) 上,View.post() 將不再那么靠譜了,你 post() 出去的 Runnable ,可能永遠(yuǎn)也不會(huì)有機(jī)會(huì)得到執(zhí)行。我們先來看看它們的細(xì)節(jié)。

二、post 在 7.0 的差異

2.1 post 方法的差異

前面提到,這個(gè)問題只出現(xiàn)在 Android 7.0 上。那么就先從源碼分析 Android 7.0 到底對(duì) View.post() 做了什么改動(dòng)。

用 Diff 看一下它們的差異,左邊是 Api Level 24(以下簡稱 Api24) 的代碼,右邊是 Api level 23-(以下簡稱 Api23) 的代碼。

很明顯的可以看出來,它們只有在 mAttachInfo 為 null 的時(shí)候,執(zhí)行的邏輯才會(huì)有差異。

Api24 中,會(huì)調(diào)用 getRunQueue().post(action),而 Api23 會(huì)調(diào)用 ViewRootImpl.getRunQueue().post(action) 方法,他們的差異就在這里。

2.2 Api23 post 的細(xì)節(jié)

先簡單理解一下,ViewRootImpl 是什么。

ViewRootImpl 可以理解是一個(gè) Activity 的 ViewTree 的根節(jié)點(diǎn)的實(shí)例。每個(gè) ViewRootImpl 就是用來管理 DecorView 和 ViewTree。

ViewRootImpl 中,用來承載 Runnable 的隊(duì)列是 sRunQueues ,它一個(gè)靜態(tài)的變量,也就是說在 App 的生命周期內(nèi),ViewRootImpl 中的這個(gè)消息隊(duì)列都是同一個(gè)。

再來看看前面提到的 ViewRootImpl.getRunQueue().post() 到底干了什么?

post() 方法只是單純的將它包裝成一個(gè) HandlerAction 對(duì)象,然后放入 mActions 這個(gè) ArrayList 中。繼續(xù)追查下去就需要知道 mActions 中添加的 HandlerAction 在何時(shí)被消費(fèi)掉了。

消費(fèi) HandlerAction 的地方,是 executeActions() 方法。

它最終,還是調(diào)用的 handler.postDelayed() ,這沒什么好說的,關(guān)鍵點(diǎn)在于 executeAction() 方法,是在什么時(shí)候被調(diào)用的。

executeAction() 是被 TraversalRunnable 調(diào)用 doTraversa() ,在doTraversa() 方法中,進(jìn)行調(diào)用的。而 TraversalRunnable 又是通過 Choreographer.postCallBack() 去循環(huán)調(diào)用的。這個(gè) Choreographer 通過 doScheduleCallback() 發(fā)送一個(gè) MSG_DO_SCHEDULE_CALLBACK 類型的消息循環(huán)調(diào)用,間隔就是一個(gè) VSync 的間隔。

關(guān)于 Choreographer ,不是本文的重點(diǎn),有興趣可以單獨(dú)了解一下。

而在 Api23 以下,executeAction() 是會(huì)被循環(huán)調(diào)用,基本上其內(nèi)的 mActions 中,只要有未執(zhí)行的 Runnable 立刻就會(huì)被消費(fèi)掉。

所以在 Api23 以下的設(shè)備上,無論如何 View.post() 基本上是靠譜的,post 出去的 Runnable 都會(huì)有機(jī)會(huì)執(zhí)行到。

2.3 Api24 的細(xì)節(jié)

再來看看在 Api24 中的實(shí)現(xiàn)細(xì)節(jié),在 Api24 中,調(diào)用的是 getRunQueue().post() 方法,它操作的是一個(gè) HandlerActionQueue 對(duì)象。

內(nèi)部的結(jié)構(gòu)其實(shí)和 Api23 很像,也是維護(hù)了一個(gè) HandlerAction 的數(shù)組 mActions 。

最終消費(fèi) mActions 的地方,依然是一個(gè) executeActions() 方法。

回到根本的問題,executeActions() 方法在什么時(shí)機(jī)會(huì)被調(diào)用到,繼續(xù)追查可以看到它在 View.dispatchAttachedToWindow() 方法中,會(huì)被調(diào)用。

既然,executeActions() 方法,在 Api24 及以上,只會(huì)在 dispatchAttachedToWindow() 的方法中,才有機(jī)會(huì)被調(diào)用到,而 View.dispatchAttachedToWindow() 方法,只有在這個(gè) View 通過 addView() 方法,或者原本寫在頁面布局的 xml 中(實(shí)際上也是調(diào)用的 addView()),加入到一個(gè) ViewGroup 的時(shí)候,才會(huì)被調(diào)用到。

這就導(dǎo)致,如果你只是通過 new 或者使用 LayoutInflater 創(chuàng)建了一個(gè) View ,而沒有將它通過 addView() 加入到 布局視圖中去,你通過這個(gè) View.post() 出去的 Runnable ,將永遠(yuǎn)不會(huì)被執(zhí)行到。 這也就是到了 Api24 下,View.post() 表現(xiàn)的現(xiàn)象不一致的緣故。

三、舉個(gè)例子說明問題

既然只是復(fù)現(xiàn)這個(gè)問題,秉承最小改動(dòng)原則,構(gòu)造一個(gè)最簡單的場(chǎng)景,單獨(dú) new 一個(gè) View 出來,然后通過它去調(diào)用 post() 方法,看看執(zhí)行的結(jié)果。

可以看到,這里直接 new 了一個(gè) View,然后 post 出去了一個(gè) Runnable ,間隔 10s 之后,將這個(gè) View 加入到根布局中。

看看在 Api 23 下的執(zhí)行效果:

可以看到,在 Api 23一下,這里是 Api19,新 new 出來的 View 對(duì)象,post 出去的 Runnable ,會(huì)立即得到執(zhí)行,不需要等待 addView() 的執(zhí)行。

再來看看在 Api24 下的執(zhí)行效果:

從執(zhí)行時(shí)間上可以看出來,post 出去的 Runnable ,并不是立即被執(zhí)行了,而是等到了 addView() 的調(diào)用之后,才被執(zhí)行的,這個(gè)中間正好被間隔了 10s。

據(jù)說這個(gè)問題,在 Android 8.0 上又被修改回去了,專門找了一款 8.0 的設(shè)備試試運(yùn)行結(jié)果,如下圖:

25 是 Android 8.0 的預(yù)覽版,這里可以看到,依然是和在 7.0 上的表現(xiàn)一樣,會(huì)等到最終 addView() 的時(shí)候再執(zhí)行,正式版不知道會(huì)不會(huì)有所改動(dòng),這個(gè)還有待驗(yàn)證。

基本上確定,受到影響的是 Android Api 24+,但是依然是開發(fā)者需要注意的,畢竟發(fā)布出去的 App ,具體運(yùn)行在什么設(shè)備上,這就不是我們能決定的了。

四、小結(jié)

View.post() 方法,在不同版本的差異,根本原因還是在于 Api23 和 Api24 中,executeActions() 方法的調(diào)用時(shí)機(jī)不同,導(dǎo)致 View 在沒有 mAttachInfo 對(duì)象的時(shí)候,表現(xiàn)不一樣了。

所以我們?cè)谑褂玫倪^程中需要慎用,區(qū)分出實(shí)際使用的場(chǎng)景,一般規(guī)范自己的代碼即可:

動(dòng)態(tài)創(chuàng)建的 View ,如果視條件去決定是否加入到根布局中,則不要使用它來調(diào)用 post() 方法。

盡量避免使用 View.post() 方法,可以直接使用 Handler.post() 方法來替代。

【本文為51CTO專欄作者“張旸”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)通過微信公眾號(hào)聯(lián)系作者獲取授權(quán)】

戳這里,看該作者更多好文

責(zé)任編輯:武曉燕 來源: 51CTO專欄
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)