前言
今天遇到一個很莫名其妙的問題,就是一個view有兩個狀態,狀態A與狀態B,在Activity的onCreate的時候先顯示狀態A,並在onCreate裏面請求接口,接口回來後顯示B。
斷點的時候,這個邏輯沒問題,但是放開斷點,很大機率顯示的是view的狀態A,原因就在於View.post()執行的時機不定,今天這篇文章就來追溯一下View.post()執行的時機。
使用場景
相信大家都知道,我們如果想在Activity.onCreate()裏面計算寬高,就可以使用View.post()的方法,並且,在沒有Handler的時候,我們可以拿某個View代替Handler的功能,使用View.post(),但是View.post()並不一定會執行,下面我們來看下源碼。
源碼跟蹤
/**
* <p>Causes the Runnable to be added to the message queue.
* The runnable will be run on the user interface thread.</p>
*
* @param action The Runnable that will be executed.
*
* @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*
* @see #postDelayed
* @see #removeCallbacks
*/
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是有返回值的,不過這個返回值其實沒有意義,註釋也說了,一般是隊列退出的時候才這樣。
從這裏可以看到,這裏的attachInfo很關鍵,這個attchInfo是什麼東西呢?
其實,在之前的博客隱約提過這個,attachInfo是在ViewRootImpl裏面賦值的,ViewRootImpl是個很複雜的類,幾乎跟view有關的所有核心代碼都在這裏面,他也是連接WMS和View的橋樑,是所有View的根View,注意,ViewRootImpl並不是個View,只是View。網上關於ViewRoot的解析文章汗牛充棟,樓主也是看他們的文章,這裏就不打算自己寫了,只聊聊我們這裏的問題。
View中AttchInfo的賦值是在performTraversals裏面
//ViewRootImpl performTraversals()
Rect frame = mWinFrame;
if (mFirst) {
.....省略代碼
host.dispatchAttachedToWindow(mAttachInfo, 0);
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
dispatchApplyInsets(host);
//Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn);
} else {
...
}
//View dispatchOnWindowAttachedChange()
public void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
...省略
if (mRunQueue != null) {
//這裏就是消耗的地方
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
}
但是,ViewRotImpl裏面的AttachInfo的卻是在初始化裏面
public ViewRootImpl(Context context, Display display) {
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,context);
}
由此可見,mAttachInfo的賦值,是在perfomTravels裏面,而perfomTravels又是在響應onVsync信號的時候才賦值,而監聽垂直信號量又是在Activity的生命週期performRusume()裏面的wm.addView()裏面執行的。由此可見,在onCreate裏面執行的post,並不會執行,而是會走getRunQueue().post(action);
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;
}
/**
* Returns the queue of runnable for this view.
*
* @return the queue of runnables for this view
*/
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
HandlerActionQueue是什麼呢?官方對於這個類的解釋已經很清楚了
Class used to enqueue pending work from Views when no Handler is attached.
也就是說,是在View沒有被attach的時候,臨時存儲工作的。
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];
}
//這個地方其實有一個小小的疑惑,那就是GrowingArrayUtils是V7包裏面的類,android原生包並沒有,這裏是怎麼調用成功的。又或者GrowingArrayUtils其實存在只是被隱藏了?
mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
mCount++;
}
}
HanderAction就是用來存儲Runnable和delay時間的。
而HandlerAction的消耗就是在executeActions,上面已經在代碼裏面寫過,就是在View.dispatchAttachedToWindow()裏面
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;
}
}
好了,這個問題基本在這裏就解決了,那麼,上面幾個問題答案也就出來了。
Activity.onCreate()裏面計算寬高
弄清楚這個問題,我們就需要搞清楚,在dispatchAttachedToWindow之前,view寬高是否已經測量好了。這個問題其實也很簡單,留給讀者自己去閱讀源碼,因爲View的測量繪製時超級麻煩的情況,過段時間再把這一塊補上。
View.post()並不一定會執行
這就是因爲view不一定會AttachTo到ViewRootImpl上面,如果沒有添加到這個ViewRoot裏面,顯然就不會執行了。並且,前面也分析過,這個執行時間是無法確定的,所以經常出現斷點時可以,但是放開斷點就有問題的情況。
後記
這個知識點很危險,如果不知道,就經常容易用錯,由此可見,平時多看源碼,多看博客是多麼重要。