一.背景
???????別人問:View.post() 為什么能夠獲取到 View 的寬高 ?
???????別人答案:post()是在View繪制完成后執(zhí)行;
???????仔細一想:View必須在繪制完成后才能得到寬高,那么post()又是在View繪制完成后執(zhí)行,那么post()內(nèi)就能夠獲取到View的寬高。
???????帶著問題和答案,沒有別的途徑,只能通過源碼來找到答案,下面就直接從View.post()執(zhí)行開始:
二.View.post()
???????通過源碼,先看一下View內(nèi)部post()執(zhí)行邏輯:
a.View.java
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;
}
???????我們看到,在post()內(nèi)部有兩個分支可能執(zhí)行:
???????1.attachInfo對象不為空,執(zhí)行通過attachInfo內(nèi)部的Handler執(zhí)行Runnable;
???????2.若attachInfo為空,則執(zhí)行getRunQueue().post();
???????分支1涉及到AttachInfo,該AttachInfo涉及到View的繪制流程,后面進行詳細分析,先分析分支2中的getRunQueue():
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
???????getRunQueue()內(nèi)部創(chuàng)建了一個HandlerActionQueue對象,再看一下HandlerActionQueue這個類:
b.HandlerActionQueue.java
public class HandlerActionQueue {
private HandlerAction[] mActions;
private int mCount;
public void post(Runnable action) {
postDelayed(action, 0);
}
public void postDelayed(Runnable action, long delayMillis) {
final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
synchronized (this) {
if (mActions == null) {
mActions = new HandlerAction[4];
}
mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
mCount++;
}
}
.......
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;
}
}
.......
private static class HandlerAction {
final Runnable action;
final long delay;
public HandlerAction(Runnable action, long delay) {
this.action = action;
this.delay = delay;
}
public boolean matches(Runnable otherAction) {
return otherAction == null && action == null
|| action != null && action.equals(otherAction);
}
}
}
???????HandlerActionQueue是一個初始容量是4的 HandlerAction數(shù)組。HandlerAction有兩個成員變量:需要執(zhí)行的Runnable和延遲執(zhí)行的時間。
???????隊列的執(zhí)行邏輯在 executeActions(handler) 方法中,通過傳入的handler進行任務分發(fā)。
???????前面分析到:在View.java內(nèi)的getRunQueue()內(nèi)部創(chuàng)建了HandlerActionQueue對象mRunQueue,那么executeActions()的調用也應該是在View內(nèi)部,通過搜索發(fā)現(xiàn),在View.java內(nèi)部有一處調用到了executeActions()方法,一起看一下dispatchAttachedToWindow():
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
if (mOverlay != null) {
mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
}
.......
// Transfer all pending runnables.
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
performCollectViewAttributes(mAttachInfo, visibility);
onAttachedToWindow();
......
......
}
???????在dispatchAttachedToWindow()內(nèi)部主要執(zhí)行了兩件事:
???????1.對mAttachInfo進行賦值,也就是說:如果沒有執(zhí)行dispatchAttachedToWindow(),那么mAttachInfo為空,post()內(nèi)部也就不會執(zhí)行分支1;
???????2.執(zhí)行HandlerActionQueue的executeActions()方法來執(zhí)行runnable;
???????那么接下來需要分析dispatchAttachedToWindow()是在什么地方被調用的?熟悉View繪制流程可以參考之前寫的一篇文章Android Activity 顯示詳解,可以清楚的看到在ViewRootImpl內(nèi)部會執(zhí)行dispatchAttachedToWindow(),再一起看一下:
c.ViewRootImpl.java
???????先看一下ViewRootImpl的創(chuàng)建,即ViewRootImpl的構造方法,此處只分析跟View.post()相關的知識點:
public ViewRootImpl(Context context, Display display) {
.......
.......
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
context);
.....
mChoreographer = Choreographer.getInstance();
......
}
???????可以看到,在ViewRootImpl的構造方法內(nèi)部,創(chuàng)建了AttachInfo對象和Choreographer實例(執(zhí)行繪制流程Runnable)。AttachInfo傳入了mHandler,后續(xù)的任務執(zhí)行都是通過該Handler,簡單看一下mHandler的創(chuàng)建:
final ViewRootHandler mHandler = new ViewRootHandler();
final class ViewRootHandler extends Handler {
}
???????該Handler是使用的是默認的Looper,即主線程的Looper,因此該Handler進行的消息處理是工作在主線程。
???????然后根據(jù)繪制的流程,一步一步的最終執(zhí)行到熟悉的performTraversals()方法:
private void performTraversals() {
final View host = mView;
......
......
host.dispatchAttachedToWindow(mAttachInfo, 0);
......
//Measure()
//Layout()
//Draw()
......
}
???????在performTraversals()內(nèi)部執(zhí)行了host的dispatchAttachedToWindow()方法,將創(chuàng)建的AttachInfo對象作為參數(shù)傳入,該host是ViewGroup(setContentView加載的LayoutID),看一下ViewGroup內(nèi)dispatchAttachedToWindow()的執(zhí)行邏輯:
@Override
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
.....
super.dispatchAttachedToWindow(info, visibility);
.....
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()));
}
......
}
???????可以看到,在ViewGroup內(nèi)部的dispatchAttachedToWindow()會遍歷子View,來執(zhí)行子View的dispatchAttachedToWindow()方法,前面分析到,在View的dispatchAttachedToWindow()內(nèi)部執(zhí)行了AttachInfo對象的賦值操作,所有的子View共用一個AttachInfo對象。
???????開始看到這里有一點疑惑:明明是先調用的 dispatchAttachedToWindow() ,再進行的Measure()、Layout()、Draw(),為什么 dispatchAttachedToWindow() 中可以獲取到View的寬高呢?
???????performTraversals()是在主線程消息隊列的一次消息處理過程中執(zhí)行的,而dispatchAttachedToWindow()間接調用的mRunQueue.executeActions() 發(fā)送的任務也是通過Handler發(fā)送到主線程消息隊列的,由于Handler執(zhí)行的有序性,那么它的執(zhí)行就一定在這次的performTraversals()方法執(zhí)行完之后,因此在post()里面是可以獲取View的寬高的。
簡單總結
???????執(zhí)行View.post()后,根據(jù)AttachInfo是否為空,即ViewRootImpl是否已經(jīng)創(chuàng)建,View.post() 會執(zhí)行不同的邏輯:
???????分支1:ViewRootImpl 已經(jīng)創(chuàng)建,即mAttachInfo已經(jīng)初始化,直接通過Handler發(fā)送消息來執(zhí)行任務。
???????分支2:ViewRootImpl 未創(chuàng)建,即View尚未開始繪制,會將任務保存為 HandlerAction,暫存在HandlerActionQueue 中,等到View開始繪制,執(zhí)行 performTraversal() 方法時,在 dispatchAttachedToWindow() 方法中通過Handler分發(fā)執(zhí)行HandlerActionQueue中暫存的任務。
流程圖如下:

三. 問題延伸
???????通過上面的分析,我們可以看到,在執(zhí)行View.post()后,如果AttachInfo沒有創(chuàng)建,則會先加入HandlerActionQueue中,不會立刻執(zhí)行,需要等View繪制完成才能執(zhí)行post(),結合View的繪制流程,會有以下延伸問題:
a.為什么onCreate()使用view.post()無法立刻執(zhí)行任務?
???????還是從View繪制原理出發(fā),View的繪制是在onResume()后進行繪制的,在onCreate()時,View還未進行繪制,所以AttachInfo也就沒有被創(chuàng)建,那么就只能將起放入隊列里面,后續(xù)等繪制完成后才能執(zhí)行任務。
b.若只是創(chuàng)建一個 View,調用它的post(),那么post的任務會不會被執(zhí)行?
View view = new View(this);
view.post(new Runnable() {
@Override
public void run() {
//...................
}
});
???????還是從View繪制原理出發(fā),不會。每個View中post() 需執(zhí)行的任務,必須得添加到窗口視圖--->執(zhí)行繪制流程,任務才會被post到消息隊列里去等待執(zhí)行,即依賴于dispatchAttachedToWindow ();
???????若View未添加到窗口視圖,那么就不會走繪制流程,post() 添加的任務最終不會被post到消息隊列里,即得不到執(zhí)行。(但會保存到HandlerAction數(shù)組里)
???????上述例子,因為它沒有被添加到窗口視圖,所以不會走繪制流程,所以該任務最終不會被post到消息隊列里執(zhí)行,執(zhí)行addView(view)就可以進行繪制然后post()任務就可以執(zhí)行了。
c.View.pos()傳入的任務被執(zhí)行的有效期
???????通過post()代碼可以看到,只要attachInfo對象不為空,那么任務就會被執(zhí)行,attachInfo賦值是在dispatchAttachedToWindow(),那么置空是在什么地方呢?
void dispatchDetachedFromWindow() {
AttachInfo info = mAttachInfo;
if (info != null) {
int vis = info.mWindowVisibility;
if (vis != GONE) {
onWindowVisibilityChanged(GONE);
......
}
}
onDetachedFromWindow();
onDetachedFromWindowInternal();
.......
mAttachInfo = null;
.....
}
???????通過分析源碼知道,Activity在執(zhí)行Destroy()后的執(zhí)行流程如下:

???????通過以上可以看到:View.post() 任務被執(zhí)行的有效期是在 Activity 生命周期 onDestory()后。
以上就是對View.post()執(zhí)行原理及延伸問題的分析!
???????感謝該文章http://www.itdecent.cn/p/e2a8cd384eda作者的分析。