從view.post再看消息處理
大家都知道view.post可以在主線程執(zhí)行一段Runnable,并且相比自己定義Handler 而言,更加簡(jiǎn)潔方便。然而這個(gè)方法跟handler.post 有怎樣的區(qū)別。 本文從源碼角度簡(jiǎn)單探究一下兩種方式的實(shí)現(xiàn)細(xì)節(jié)。
view.post 實(shí)現(xiàn)
該方法的實(shí)現(xiàn)非常簡(jiǎn)單
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
可以看到第一個(gè)邏輯分支,仍然是通過(guò)handler進(jìn)行消息分發(fā)。 而第二個(gè)分之是通過(guò)一個(gè)RunQueue進(jìn)行的分發(fā)動(dòng)作。
第一個(gè)邏輯分支相對(duì)簡(jiǎn)易,是直接通過(guò)該view 獲取的attachInfo中的Handler進(jìn)行處理。而這個(gè)attachinfo 是在
void dispatchAttachedToWindow(AttachInfo info, int visibility)
方法中進(jìn)行賦值的,只有當(dāng)該方法被調(diào)用后,才會(huì)執(zhí)行第一個(gè)邏輯分支。
再看第二個(gè)邏輯分支。
RunQueue 對(duì)應(yīng)的事實(shí)上是 HandlerActionQueue 類的一個(gè)對(duì)象。 HandlerActionQueue 的post方法中會(huì)對(duì)將要發(fā)送的Runnable進(jìn)行緩存,直到executeAction(Handler handler)方法被外部調(diào)用,則會(huì)依次將待處理的Runnable 添加到入?yún)⒌腍andler 消息隊(duì)列中執(zhí)行。
public void executeActions(Handler handler) {
synchronized (this) {
final HandlerAction[] actions = mActions;
for (int i = 0, count = mCount; i < count; i++) {
final HandlerAction handlerAction = actions[i];
handler.postDelayed(handlerAction.action, handlerAction.delay);
}
mActions = null;
mCount = 0;
}
}
那么這個(gè)方法是什么時(shí)候被調(diào)用的呢。一共有兩個(gè)地方
- 第一是在ViewRootImpl的performTraversal方法中,傳入mAttachInfo的handler執(zhí)行。
- 第二是在View 的dispatchAttachedToWindow方法中,也是通過(guò)attachInfo的handler來(lái)執(zhí)行緩存的Runnable 方法
熟悉 ViewRootImpl 的同學(xué)知道,這是整個(gè)視窗的根視圖,并且在每一幀渲染時(shí),它的performTravsal會(huì)被調(diào)用用以整個(gè)頁(yè)面的布局。然而我們實(shí)際使用中,往往是通過(guò)一個(gè)頂層view進(jìn)行post動(dòng)作,因而更多地是出發(fā)第二的方法。
而對(duì)于第二個(gè)方法,我們又和第一個(gè)邏輯的判斷條件分支殊途同歸了。
至此,我們能夠確定的是 dispatchAttachedToWindow 執(zhí)行后,view.post 的Runnable才能夠被執(zhí)行。
dispatchAttachedToWindow
dispatchAttachedToWindow 是Android視圖渲染的關(guān)鍵方法。感興趣的同學(xué)可以查閱相關(guān)資料,這里我們只是簡(jiǎn)述一下它的執(zhí)行機(jī)制。
我們知道視圖的根實(shí)際上是一個(gè)ViewGroup, 它的dispatchAttachedToWindow方法為
@Override
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
super.dispatchAttachedToWindow(info, visibility);
mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
final View child = children[i];
child.dispatchAttachedToWindow(info,
combineVisibility(visibility, child.getVisibility()));
}
final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
for (int i = 0; i < transientCount; ++i) {
View view = mTransientViews.get(i);
view.dispatchAttachedToWindow(info,
combineVisibility(visibility, view.getVisibility()));
}
}
即,遍歷所有子view 一次分發(fā)attachToWindow這一個(gè)函數(shù)通知,使得子view一次執(zhí)行。在View框架整個(gè)調(diào)用過(guò)程 ,如果是非葉節(jié)點(diǎn),首先調(diào)用父類的dispatchAttachedToWindow,然后調(diào)用子節(jié)點(diǎn)的dispatchAttachedToWindow;如果是葉節(jié)點(diǎn),則會(huì)調(diào)用View的dispatchAttachedToWindow,整個(gè)調(diào)用過(guò)程可以看成樹(shù)的先序遍歷。而在ViewRootImpl中,只有當(dāng)mFirst == true時(shí),也就是第一次布局時(shí),才會(huì)調(diào)用根的dispathAttachedToWindow方法。也就印證了這個(gè)函數(shù)名,當(dāng)視圖第一次被添加到視窗時(shí)執(zhí)行。
綜上, 調(diào)用view.post方法,如果對(duì)應(yīng)的view 并沒(méi)有被添加到視窗(i.e. 第一次performTraversal 沒(méi)有被執(zhí)行),則該Runnable 會(huì)被緩存至視圖添加完后,再被添加到MainLooper的消息隊(duì)列的對(duì)應(yīng)位置執(zhí)行。 對(duì)比直接使用Handler 進(jìn)行post 的動(dòng)作,更加適合操作一些視圖元素。