emmm,大伙都知道,子線程是不能進(jìn)行 UI 操作的,或者很多場(chǎng)景下,一些操作需要延遲執(zhí)行,這些都可以通過(guò) Handler 來(lái)解決。但說(shuō)實(shí)話,實(shí)在是太懶了,總感覺(jué)寫 Handler 太麻煩了,一不小心又很容易寫出內(nèi)存泄漏的代碼來(lái),所以為了偷懶,我就經(jīng)常用 View.post() or View.postDelay() 來(lái)代替 Handler 使用。
創(chuàng)新互聯(lián)建站專業(yè)為企業(yè)提供安福網(wǎng)站建設(shè)、安福做網(wǎng)站、安福網(wǎng)站設(shè)計(jì)、安福網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁(yè)設(shè)計(jì)與制作、安福企業(yè)網(wǎng)站模板建站服務(wù),十年安福做網(wǎng)站經(jīng)驗(yàn),不只是建網(wǎng)站,更提供有價(jià)值的思路和整體網(wǎng)絡(luò)服務(wù)。但用多了,總有點(diǎn)心虛,View.post() 會(huì)不會(huì)有什么隱藏的問(wèn)題?所以趁有點(diǎn)空余時(shí)間,這段時(shí)間就來(lái)梳理一下,View.post() 原理到底是什么,內(nèi)部都做了啥事。
提問(wèn)
開始看源碼前,先提幾個(gè)問(wèn)題,帶著問(wèn)題去看源碼應(yīng)該會(huì)比較有效率,防止閱讀源碼過(guò)程中,陷得太深,跟得太偏了。
Q1: 為什么 View.post() 的操作是可以對(duì) UI 進(jìn)行操作的呢,即使是在子線程中調(diào)用 View.post()?
Q2:網(wǎng)上都說(shuō) View.post() 中的操作執(zhí)行時(shí),View 的寬高已經(jīng)計(jì)算完畢,所以經(jīng)??匆?jiàn)在 Activity 的 onCreate() 里調(diào)用 View.post() 來(lái)解決獲取 View 寬高為0的問(wèn)題,為什么可以這樣做呢?
Q3:用 View.postDelay() 有可能導(dǎo)致內(nèi)存泄漏么?
ps:本篇分析的源碼基于 andoird-25 版本,版本不一樣源碼可能有些區(qū)別,大伙自己過(guò)源碼時(shí)可以注意一下。另,下面分析過(guò)程有點(diǎn)長(zhǎng),慢慢看哈。
源碼分析
好了,就帶著這幾個(gè)問(wèn)題來(lái)跟著源碼走吧。其實(shí),這些問(wèn)題大伙心里應(yīng)該都有數(shù)了,看源碼也就是為了驗(yàn)證心里的想法。第一個(gè)問(wèn)題,之所以可以對(duì) UI 進(jìn)行操作,那內(nèi)部肯定也是通過(guò) Handler 來(lái)實(shí)現(xiàn)了,所以看源碼的時(shí)候就可以看看內(nèi)部是如何對(duì) Handler 進(jìn)行封裝的。而至于剩下的問(wèn)題,那就在看源碼過(guò)程中順帶看看能否找到答案吧。
View.post()
View.post() 方法很簡(jiǎn)單,代碼很少。那我們就一行行的來(lái)看。
如果 mAttachInfo 不為空,那就調(diào)用 mAttachInfo.mHanlder.post() 方法,如果為空,則調(diào)用 getRunQueue().post() 方法。
那就找一下,mAttachInfo 是什么時(shí)候賦值的,可以借助 AS 的 Ctrl + F
查找功能,過(guò)濾一下 mAttachInfo =
,注意 =
號(hào)后面還有一個(gè)空格,否則你查找的時(shí)候會(huì)發(fā)現(xiàn)全文有兩百多處匹配到。我們只關(guān)注它是什么時(shí)候賦值的,使用的場(chǎng)景就不管了,所以過(guò)濾條件可以細(xì)一點(diǎn)。這樣一來(lái),全文就只有兩處匹配:
一處賦值,一處置空,剛好又是在對(duì)應(yīng)的一個(gè)生命周期里:
dispatchAttachedToWindow() 下文簡(jiǎn)稱 attachedToWindow
dispatchDetachedFromWindow() 下文簡(jiǎn)稱 detachedFromWindow。
所以,如果 mAttachInfo 不為空的時(shí)候,走的就是 Handler 的 post(),也就是 View.post() 在這種場(chǎng)景下,實(shí)際上就是調(diào)用的 Handler.post(),接下去就是搞清楚一點(diǎn),這個(gè) Handler 是哪里的 Handler,在哪里初始化等等,但這點(diǎn)可以先暫時(shí)放一邊,因?yàn)?mAttachInfo 是在 attachedToWindow 時(shí)才賦值的,所以接下去關(guān)鍵的一點(diǎn)是搞懂 attachedToWindow 到 detachedFromWindow 這個(gè)生命周期分別在什么時(shí)候在哪里被調(diào)用了。
雖然我們現(xiàn)在還不清楚,attachedToWindow 到底是什么時(shí)候被調(diào)用的,但看到這里我們至少清楚一點(diǎn),在 Activity 的 onCreate() 期間,這個(gè) View 的 attachedToWindow 應(yīng)該是還沒(méi)有被調(diào)用,也就是 mAttachInfo 這時(shí)候還是為空,但我們?cè)?onCreate() 里執(zhí)行 View.post() 里的操作仍然可以保證是在 View 寬高計(jì)算完畢的,也就是開頭的問(wèn)題 Q2,那么這點(diǎn)的原理顯然就是在另一個(gè) return 那邊的方法里了:getRunQueue().post()。
那么,我們就先解決 Q2 吧,為什么 View.post() 可以保證操作是在 View 寬高計(jì)算完畢之后呢?跟進(jìn) getRunQueue() 看看:
getRunQueue().post()
所以調(diào)用的其實(shí)是 HandlerActionQueue.post() 方法,那么我們?cè)倮^續(xù)跟進(jìn)去看看:
post(Runnable) 方法內(nèi)部調(diào)用了 postDelayed(Runnable, long),postDelayed() 內(nèi)部則是將 Runnable 和 long 作為參數(shù)創(chuàng)建一個(gè) HandlerAction 對(duì)象,然后添加到 mActions 數(shù)組里。下面先看看 HandlerAction:
很簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu),就一個(gè) Runnable 成員變量和一個(gè) long 成員變量。這個(gè)類作用可以理解為用于包裝 View.post(Runnable) 傳入的 Runnable 操作的,當(dāng)然因?yàn)檫€有 View.postDelay() ,所以就還需要一個(gè) long 類型的變量來(lái)保存延遲的時(shí)間了,這樣一來(lái)這個(gè)數(shù)據(jù)結(jié)構(gòu)就不難理解了吧。
所以,我們調(diào)用 View.post(Runnable) 傳進(jìn)去的 Runnable 操作,在傳到 HandlerActionQueue 里會(huì)先經(jīng)過(guò) HandlerAction 包裝一下,然后再緩存起來(lái)。至于緩存的原理,HandlerActionQueue 是通過(guò)一個(gè)默認(rèn)大小為4的數(shù)組保存這些 Runnable 操作的,當(dāng)然,如果數(shù)組不夠用時(shí),就會(huì)通過(guò) GrowingArrayUtils 來(lái)擴(kuò)充數(shù)組,具體算法就不繼續(xù)看下去了,不然越來(lái)越偏。
到這里,我們先來(lái)梳理下:
當(dāng)我們?cè)?Activity 的 onCreate() 里執(zhí)行 View.post(Runnable) 時(shí),因?yàn)檫@時(shí)候 View 還沒(méi)有 attachedToWindow,所以這些 Runnable 操作其實(shí)并沒(méi)有被執(zhí)行,而是先通過(guò) HandlerActionQueue 緩存起來(lái)。
那么到什么時(shí)候這些 Runnable 才會(huì)被執(zhí)行呢?我們可以看看 HandlerActionQueue 這個(gè)類,它的代碼不多,里面有個(gè) executeActions() 方法,看命名就知道,這方法是用來(lái)執(zhí)行這些被緩存起來(lái)的 Runnable 操作的:
哇,看到重量級(jí)的人物了:Handler??磥?lái)被緩存起來(lái)沒(méi)有執(zhí)行的 Runnable 最后也還是通過(guò) Hnadler 來(lái)執(zhí)行的。那么,這個(gè) Handler 又是哪里的呢?看來(lái)關(guān)鍵點(diǎn)還是這個(gè)方法在哪里被調(diào)用了,那就找找看:
借助 AS 的 Ctrl + Alt + F7
快捷鍵,可以查找 SDK 里的某個(gè)方法在哪些地方被調(diào)用了。
很好,找到了,而且只找到這個(gè)地方。其實(shí),這個(gè)快捷鍵有時(shí)并沒(méi)有辦法找到一些方法被調(diào)用的地方,這也是源碼閱讀過(guò)程中令人頭疼的一點(diǎn),因?yàn)闆](méi)法找到這些方法到底在哪些地方被調(diào)用了,所以很難把流程梳理下來(lái)。如果方法是私有的,那很好辦,就用 Ctrl + F
在這個(gè)類里找一下就可以,如果匹配結(jié)果太多,那就像開頭那樣把過(guò)濾條件詳細(xì)一點(diǎn)。如果方法不是私有的,那真的就很難辦了,這也是一開始找到 dispatchAttachedToWindow() 后為什么不繼續(xù)跟蹤下去轉(zhuǎn)而來(lái)分析Q2:getRunQueue() 的原因,因?yàn)橛?AS 找不到 dispatchAttachedToWindow() 到底在哪些地方被誰(shuí)調(diào)用了。哇,好像又扯遠(yuǎn)了,回歸正題回歸正題。
emmm,看來(lái)這里也繞回來(lái)了,dispatchAttachedToWindow() 看來(lái)是個(gè)關(guān)鍵的節(jié)點(diǎn)。
那到這里,我們?cè)俅蝸?lái)梳理一下:
我們使用 View.post() 時(shí),其實(shí)內(nèi)部它自己分了兩種情況處理,當(dāng) View 還沒(méi)有 attachedToWindow 時(shí),通過(guò) View.post(Runnable) 傳進(jìn)來(lái)的 Runnable 操作都先被緩存在 HandlerActionQueue,然后等 View 的 dispatchAttachedToWindow() 被調(diào)用時(shí),就通過(guò) mAttachInfo.mHandler 來(lái)執(zhí)行這些被緩存起來(lái)的 Runnable 操作。從這以后到 View 被 detachedFromWindow 這段期間,如果再次調(diào)用 View.post(Runnable) 的話,那么這些 Runnable 不用再緩存了,而是直接交給 mAttachInfo.mHanlder 來(lái)執(zhí)行。
以上,就是到目前我們所能得知的信息。這樣一來(lái),Q2 是不是漸漸有一些頭緒了:View.post(Runnable) 的操作之所以可以保證肯定是在 View 寬高計(jì)算完畢之后才執(zhí)行的,是因?yàn)檫@些 Runnable 操作只有在 View 的 attachedToWindow 到 detachedFromWiondow 這期間才會(huì)被執(zhí)行。
那么,接下去就還剩兩個(gè)關(guān)鍵點(diǎn)需要搞清楚了:
dispatchAttachedToWindow() 是什么時(shí)候被調(diào)用的? mAttachInfo 是在哪里初始化的? dispatchAttachedToWindow() & mAttachInfo
只借助 AS 的話,很難找到 dispatchAttachedToWindow() 到底在哪些地方被調(diào)用。所以,到這里,我又借助了 Source Insight 軟件。
很棒!找到了四個(gè)被調(diào)用的地方,三個(gè)在 ViewGroup 里,一個(gè)在 ViewRootImpl.performTraversals() 里。找到了就好,接下去繼續(xù)用 AS 來(lái)分析吧,Source Insight 用不習(xí)慣,不過(guò)分析源碼時(shí)確實(shí)可以結(jié)合這兩個(gè)軟件。
哇,懵逼,完全懵逼。我就想看個(gè) View.post(),結(jié)果跟著跟著,跟到這里來(lái)了。ViewRootImpl 我在分析Android KeyEvent 點(diǎn)擊事件分發(fā)處理流程時(shí)短暫接觸過(guò),但這次顯然比上次還需要更深入去接觸,哎,力不從心啊。
我只能跟大伙肯定的是,mView 是 Activity 的 DecorView。咦~,等等,這樣看來(lái) ViewRootImpl 是調(diào)用的 DecorView 的 dispatchAttachedToWindow() ,但我們?cè)谑褂?View.post() 時(shí),這個(gè) View 可以是任意 View,并不是非得用 DecorView 吧。哈哈哈,這是不是代表著我們找錯(cuò)地方了?不管了,我們就去其他三個(gè)被調(diào)用的地方: ViewGroup 里看看吧:
addViewInner() 是 ViewGroup 在添加子 View 時(shí)的內(nèi)部邏輯,也就是說(shuō)當(dāng) ViewGroup addView() 時(shí),如果 mAttachInfo 不為空,就都會(huì)去調(diào)用子 View 的 dispatchAttachedToWindow(),并將自己的 mAttachInfo 傳進(jìn)去。還記得 View 的 dispatchAttachedToWindow() 這個(gè)方法么:
mAttachInfo 唯一被賦值的地方也就是在這里,那么也就是說(shuō),子 View 的 mAttachInfo 其實(shí)跟父控件 ViewGroup 里的 mAttachInfo 是同一個(gè)的。那么,關(guān)鍵點(diǎn)還是這個(gè) mAttachInfo 什么時(shí)候才不為空,也就是說(shuō) ViewGroup 在 addViewInner() 時(shí),傳進(jìn)去的 mAttachInfo 是在哪被賦值的呢?我們來(lái)找找看:
咦,利用 AS 的 Ctrl + 左鍵
怎么找不到 mAttachInfo 被定義的地方呢,不管了,那我們用 Ctrl + F
搜索一下在 ViewGroup 類里 mAttachInfo 被賦值的地方好了:
咦,怎么一個(gè)地方也沒(méi)有。難道說(shuō),這個(gè) mAttachInfo 是父類 View 定義的變量么,既然 AS 找不到,我們換 Source Insight 試試:
還真的是,ViewGroup 是繼承的 View,并且處于同一個(gè)包里,所以可以直接使用該變量,那這樣一來(lái),我們豈不是又繞回來(lái)了。前面說(shuō)過(guò),dispatchAttachedToWindow() 在 ViewGroup 里有三處調(diào)用的地方,既然 addViewInner() 這里的看不出什么,那去另外兩個(gè)地方看看:
剩下的兩個(gè)地方就都是在 ViewGroup 重寫的 dispatchAttachedToWindow() 方法里了,這代碼也很好理解,在該方法被調(diào)用的時(shí)候,先執(zhí)行 super 也就是 View 的 dispatchAttachedToWindow() 方法,還沒(méi)忘記吧,mAttachInfo 就是在這里被賦值的。然后再遍歷子 View,分別調(diào)用子 View 的 dispatchAttachedToWindow() 方法,并將 mAttachInfo 作為參數(shù)傳遞進(jìn)去,這樣一來(lái),子 View 的 mAttachInfo 也都被賦值了。
但這樣一來(lái),我們就繞進(jìn)死胡同了。
我們還是先來(lái)梳理一下吧:
目前,我們知道,View.post(Runnable) 的這些 Runnable 操作,在 View 被 attachedToWindow 之前會(huì)先緩存下來(lái),然后在 dispatchAttachedToWindow() 被調(diào)用時(shí),就將這些緩存下來(lái)的 Runnable 通過(guò) mAttachInfo 的 mHandler 來(lái)執(zhí)行。在這之后再調(diào)用 View.post(Runnable) 的話,這些 Runnable 操作就不用再被緩存了,而是直接交由 mAttachInfo 的 mHandler 來(lái)執(zhí)行。
所以,我們得搞清楚 dispatchAttachedToWindow() 在什么時(shí)候被調(diào)用,以及 mAttachInfo 是在哪被初始化的,因?yàn)樾枰浪淖兞咳?mHandler 都是些什么以及驗(yàn)證 mHandler 執(zhí)行這些 Runnable 操作是在 measure 之后的,這樣才能保證此時(shí)的寬高不為0。
然后,我們?cè)诟?dispatchAttachedToWindow() 被調(diào)用的地方時(shí),跟到了 ViewGroup 的 addViewInner() 里。在這里我們得到的信息是如果 mAttachInfo 不為空時(shí),會(huì)直接調(diào)用子 View 的 dispatchAttachedToWindow(),這樣新 add 進(jìn)來(lái)的子 View 的 mAttachInfo 就會(huì)被賦值了。但 ViewGroup 的 mAttachInfo 是父類 View 的變量,所以為不為空的關(guān)鍵還是回到了 dispatchAttachedToWindow() 被調(diào)用的時(shí)機(jī)。
我們還跟到了 ViewGroup 重寫的 dispatchAttachedToWindow() 方法里,但顯然,ViewGroup 重寫這個(gè)方法只是為了將 attachedToWindow 這個(gè)事件通知給它所有的子 View。
所以,最后,我們能得到的結(jié)論就是,我們還得再回去 ViewRootImpl 里,dispatchAttachedToWindow() 被調(diào)用的地方,除了 ViewRootImpl,我們都分析過(guò)了,得不到什么信息,只剩最后 ViewRootImpl 這里了,所以關(guān)鍵點(diǎn)肯定在這里??磥?lái)這次,不行也得上了。
ViewRootImpl.performTraversals()
這方法代碼有八百多行!!不過(guò),我們只關(guān)注我們需要的點(diǎn)就行,這樣一省略無(wú)關(guān)代碼來(lái)看,是不是感覺(jué)代碼就簡(jiǎn)單得多了。
mFirst 初始化為 true,全文只有一處賦值,所以 if(mFirst) 塊里的代碼只會(huì)執(zhí)行一次。我對(duì) ViewRootImpl 不是很懂,performTraversals() 這個(gè)方法應(yīng)該是通知 Activity 的 View 樹開始測(cè)量、布局、繪制。而 DevorView 是 Activity 視圖的根布局、View 樹的起點(diǎn),它繼承 FrameLayout,所以也是個(gè) ViewGroup,而我們之前對(duì) ViewGroup 的 dispatchAttachedToWindow() 分析過(guò)了吧,在這個(gè)方法里會(huì)將 mAttachInfo 傳給所有子 View。也就是說(shuō),在 Activity 首次進(jìn)行 View 樹的遍歷繪制時(shí),ViewRootImpl 會(huì)將自己的 mAttachInfo 通過(guò)根布局 DecorView 傳遞給所有的子 View 。
那么,我們就來(lái)看看 ViewRootImpl 的 mAttachInfo 什么時(shí)候初始化的吧:
在構(gòu)造函數(shù)里對(duì) mAttachInfo 進(jìn)行初始化,傳入了很多參數(shù),我們關(guān)注的應(yīng)該是 mHandler 這個(gè)變量,所以看看這個(gè)變量定義:
終于找到 new Handler() 的地方了,至于這個(gè)自定義的 Handler 類做了啥,我們不關(guān)心,反正通過(guò) post() 方式執(zhí)行的操作跟它自定義的東西也沒(méi)有多大關(guān)系。我們關(guān)心的是在哪 new 了這個(gè) Handler。因?yàn)槊總€(gè) Handler 在 new 的時(shí)候都會(huì)綁定一個(gè) Looper,這里 new 的時(shí)候是無(wú)參構(gòu)造函數(shù),那默認(rèn)綁定的就是當(dāng)前線程的 Looper,而這句 new 代碼是在主線程中執(zhí)行的,所以這個(gè) Handler 綁定的也就是主線程的 Looper。至于這些的原理,就涉及到 Handler 的源碼和 ThreadLocal 的原理了,就不繼續(xù)跟進(jìn)了,太偏了,大伙清楚結(jié)論這點(diǎn)就好。
這也就是為什么 View.post(Runnable) 的操作可以更新 UI 的原因,因?yàn)檫@些 Runnable 操作都通過(guò) ViewRootImpl 的 mHandler 切到主線程來(lái)執(zhí)行了。
這樣 Q1 就搞定了,終于搞定了一個(gè)問(wèn)題,不容易啊,本來(lái)以為很簡(jiǎn)單的來(lái)著。
跟到 ViewRootImpl 這里應(yīng)該就可以停住了。至于 ViewRootImpl 跟 Activity 有什么關(guān)系、什么時(shí)候被實(shí)例化的、跟 DecroView 如何綁定的就不跟進(jìn)了,因?yàn)槲乙策€不是很懂,感興趣的可以自己去看看,我在末尾會(huì)給一些參考博客。
至此,我們清楚了 mAttachInfo 的由來(lái),也知道了 mAttachInfo.mHandler,還知道在 Activity 首次遍歷 View 樹進(jìn)行測(cè)量、繪制時(shí)會(huì)通過(guò) DecorView 的 dispatchAttachedToWindow() 將 ViewRootImpl 的 mAttachInfo 傳遞給所有子 View,并通知所有調(diào)用 View.post(Runnable) 被緩存起來(lái)的 Runnable 操作可以執(zhí)行了。
但不知道大伙會(huì)不會(huì)跟我一樣還有一點(diǎn)疑問(wèn):看網(wǎng)上對(duì) ViewRootImpl.performTraversals() 的分析:遍歷 View 樹進(jìn)行測(cè)量、布局、繪制操作的代碼顯然是在調(diào)用了 dispatchAttachedToWindow() 之后才執(zhí)行,那這樣一來(lái)是如何保證 View.post(Runnable) 的 Runnable 操作可以獲取到 View 的寬高呢?明明測(cè)量的代碼 performMeasure() 是在 dispatchAttachedToWindow() 后面才執(zhí)行。
我在這里卡了很久,一直沒(méi)想明白。我甚至以為是 PhoneWindow 在加載 layout 布局到 DecorView 時(shí)就進(jìn)行了測(cè)量的操作,所以一直跟,跟到 LayoutInflater.inflate(),跟到了 ViewGroup.addView(),最后發(fā)現(xiàn)跟測(cè)量有關(guān)的操作最終都又繞回到 ViewRootImpl 中去了。
原來(lái)是自己火候不夠,對(duì) Android 的消息機(jī)制還不大理解,這篇博客前前后后寫了一兩個(gè)禮拜,就是在不斷查缺補(bǔ)漏,學(xué)習(xí)、理解相關(guān)的知識(shí)點(diǎn)。
大概的來(lái)講,就是我們的 app 都是基于消息驅(qū)動(dòng)機(jī)制來(lái)運(yùn)行的,主線程的 Looper 會(huì)無(wú)限的循環(huán),不斷的從 MessageQueue 里取出 Message 來(lái)執(zhí)行,當(dāng)一個(gè) Message 執(zhí)行完后才會(huì)去取下一個(gè) Message 來(lái)執(zhí)行。而 Handler 則是用于將 Message 發(fā)送到 MessageQueue 里,等輪到 Message 執(zhí)行時(shí),又通過(guò) Handler 發(fā)送到 Target 去執(zhí)行,等執(zhí)行完再取下一個(gè) Message,如此循環(huán)下去。
清楚了這點(diǎn)后,我們?cè)倩剡^(guò)頭來(lái)看看:
performTraversals() 會(huì)先執(zhí)行 dispatchAttachedToWindow(),這時(shí)候所有子 View 通過(guò) View.post(Runnable) 緩存起來(lái)的 Runnable 操作就都會(huì)通過(guò) mAttachInfo.mHandler 的 post() 方法將這些 Runnable 封裝到 Message 里發(fā)送到 MessageQueue 里。mHandler 我們上面也分析過(guò)了,綁定的是主線程的 Looper,所以這些 Runnable 其實(shí)都是發(fā)送到主線程的 MessageQueue 里排隊(duì),等待執(zhí)行。然后 performTraversals() 繼續(xù)往下工作,相繼執(zhí)行 performMeasure(),performLayout() 等操作。等全部執(zhí)行完后,表示這個(gè) Message 已經(jīng)處理完畢,所以 Looper 才會(huì)去取下一個(gè) Message,這時(shí)候,才有可能輪到這些 Runnable 執(zhí)行。所以,這些 Runnable 操作也就肯定會(huì)在 performMeasure() 操作之后才執(zhí)行,寬高也就可以獲取到了。畫張圖,幫助理解一下:
哇,Q2的問(wèn)題終于也搞定了,也不容易啊。本篇也算是結(jié)束了。
總結(jié)
分析了半天,最后我們來(lái)稍微小結(jié)一下:
View.post(Runnable) 內(nèi)部會(huì)自動(dòng)分兩種情況處理,當(dāng) View 還沒(méi) attachedToWindow 時(shí),會(huì)先將這些 Runnable 操作緩存下來(lái);否則就直接通過(guò) mAttachInfo.mHandler 將這些 Runnable 操作 post 到主線程的 MessageQueue 中等待執(zhí)行。
如果 View.post(Runnable) 的 Runnable 操作被緩存下來(lái)了,那么這些操作將會(huì)在 dispatchAttachedToWindow() 被回調(diào)時(shí),通過(guò) mAttachInfo.mHandler.post() 發(fā)送到主線程的 MessageQueue 中等待執(zhí)行。
mAttachInfo 是 ViewRootImpl 的成員變量,在構(gòu)造函數(shù)中初始化,Activity View 樹里所有的子 View 中的 mAttachInfo 都是 ViewRootImpl.mAttachInfo 的引用。
mAttachInfo.mHandler 也是 ViewRootImpl 中的成員變量,在聲明時(shí)就初始化了,所以這個(gè) mHandler 綁定的是主線程的 Looper,所以 View.post() 的操作都會(huì)發(fā)送到主線程中執(zhí)行,那么也就支持 UI 操作了。
dispatchAttachedToWindow() 被調(diào)用的時(shí)機(jī)是在 ViewRootImol 的 performTraversals() 中,該方法會(huì)進(jìn)行 View 樹的測(cè)量、布局、繪制三大流程的操作。
Handler 消息機(jī)制通常情況下是一個(gè) Message 執(zhí)行完后才去取下一個(gè) Message 來(lái)執(zhí)行(異步 Message 還沒(méi)接觸),所以 View.post(Runnable) 中的 Runnable 操作肯定會(huì)在 performMeaure() 之后才執(zhí)行,所以此時(shí)可以獲取到 View 的寬高。
好了,就到這里了。至于開頭所提的問(wèn)題,前兩個(gè)已經(jīng)在上面的分析過(guò)程以及總結(jié)里都解答了。而至于剩下的問(wèn)題,這里就稍微提一下:
使用 View.post(),還是有可能會(huì)造成內(nèi)存泄漏的,Handler 會(huì)造成內(nèi)存泄漏的原因是由于內(nèi)部類持有外部的引用,如果任務(wù)是延遲的,就會(huì)造成外部類無(wú)法被回收。而根據(jù)我們的分析,mAttachInfo.mHandler 只是 ViewRootImpl 一個(gè)內(nèi)部類的實(shí)例,所以使用不當(dāng)還是有可能會(huì)造成內(nèi)存泄漏的。
網(wǎng)站名稱:源碼詳解Android中View.post()用法-創(chuàng)新互聯(lián)
分享鏈接:http://jinyejixie.com/article16/deocdg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站設(shè)計(jì)、App開發(fā)、靜態(tài)網(wǎng)站、品牌網(wǎng)站建設(shè)、服務(wù)器托管、品牌網(wǎng)站設(shè)計(jì)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容