Handler.post和View.post的區別
緣起
在Android開發中,我們經常會見到下面的程式碼,比如:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
System.out.println("onCreate===");
setContentView(R.layout.activity_main);
rootBtn = findViewById(R.id.rootBtn);
// 程式碼1
UIHandler.post(new Runnable() {
@Override
public void run() {
System.out.println("Handler.post===");
}
});
// 程式碼2
rootBtn.post(new Runnable() {
@Override
public void run() {
System.out.println("View.post===");
}
});
}
你曾經有沒有想過這兩者到底有什麼區別?我該使用哪種呢?
常見的Handler.post揭祕
Handler的工作機制,網上介紹的文章太多了,這裡我就不贅述了,想繼續瞭解的同學可以參考下這篇文章:Handler原始碼分析。一句話總結就是通過Handler物件,不論是post Msg還是Runnable,最終都是構造了一個Msg物件,插入到與之對應的Looper的MessageQueue中,不同的是Running時msg物件的callback欄位設成了Runnable的值。稍後這條msg會從佇列中取出來並且得到執行,UI執行緒就是這麼一個基於事件的迴圈。所以可以看出Handler.post相關的程式碼在onCreate裡那一刻時就已經開始了執行(加入到了佇列,下次迴圈到來時就會被真正執行了)。
View.post揭祕
要解釋它的行為,我們就必須深入程式碼中去找答案了,其程式碼如下:
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
// 注意這個判斷,這個變數時機太早的話是沒值的,
// 比如在act#onCreate階段
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;
}
從上面的原始碼,我們大概可以看出mAttachInfo
欄位在這裡比較關鍵,當其有值時,其實和普通的Handler.post
就沒區別了,但有時它是沒值的,比如我們上面示例程式碼裡的onCreate階段,那麼這時執行到了getRunQueue().post(action);
這行程式碼,從這段註釋也大概可以看出來真正的執行會被延遲(這裡的Postpone
註釋);我們接著往下看看getRunQueue相關的程式碼,如下:
/** 其實這段註釋已經說的很清楚明瞭了!!!
* Queue of pending runnables. Used to postpone calls to post() until this
* view is attached and has a handler.
*/
private HandlerActionQueue mRunQueue;
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
從上面我們可以看出,mRunQueue就是View用來處理它還沒attach到window(還沒對應的handler)時,客戶程式碼發起的post呼叫的,起了一個臨時快取
的作用。不然總不能丟棄吧,這樣開發體驗就太差了!!!
緊接著,我們繼續看下HandlerActionQueue
型別的定義,程式碼如下:
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);
}
}
}
注意:這裡的原始碼部分,我們只摘錄了部分關鍵程式碼,其餘不太相關的直接略去了。
從這裡可以看出,我們前面的View.post呼叫裡的Runnable最終會被儲存在這裡的mActions
數組裡,這裡最關鍵的一點就是其executeActions
方法,因為這個方法裡我們之前post的Runnable才真正通過handler.postDelayed
方式使其進入handler對應的訊息佇列裡等待執行;
到此為止,我們還差知道View裡的mAttachInfo
欄位何時被賦值以及這裡的executeActions
方法是什麼時候被觸發的,答案就是在View的dispatchAttachedToWindow
方法,其關鍵原始碼如下:
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
...
// Transfer all pending runnables.
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
performCollectViewAttributes(mAttachInfo, visibility);
onAttachedToWindow();
...
}
而通過之前的文章,我們已經知道了此方法是當Act Resume
之後,在ViewRootImpl.performTraversals()
中觸發的,參考View.onAttachedToWindow呼叫時機分析。
總結
-
Handler.post,它的執行時間基本是等同於onCreate裡那行程式碼觸達的時間;
-
View.post,則不同,它說白了執行時間一定是在
Act#onResume
發生後才開始算的;或者換句話說它的效果相當於你上面的View.post方法是寫在Act#onResume
裡面的(但只執行一次,因為onCreate不像onResume會被多次觸發); -
當然,雖然這裡說的是
post
方法,但對應的postDelayed
方法區別也是類似的。
平時當你專案很小,MainActivity的邏輯也很簡單時是看不出啥區別的,但當act的onCreate
到onResume
之間耗時比較久時(比如2s以上),就能明顯感受到這2者的區別了,而且本身它們的實際含義也是很不同的,前者的Runnable真正執行時,可能act的整個view層次都還沒完整的measure、layout完成,但後者的Runnable執行時,則一定能保證act的view層次結構已經measure、layout並且至少繪製完成了一次。
作者:tmp_zhao
連結:https://www.jianshu.com/p/7280b2d3b4d1
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。