注:本文提到的調(diào)用Handler#post尼酿,其Handler對(duì)象都指的是用UI主線程的Looper創(chuàng)建的Handler對(duì)象图呢。
View#post方法的實(shí)現(xiàn)跟版本有關(guān),具體為:
- API level 24(Android7.0)及以上:
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;
}
- API level 23及以下:
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Assume that post will succeed later
ViewRootImpl.getRunQueue().post(action);
return true;
}
由上可知空凸,兩種版本避矢,當(dāng)attachInfo不為空時(shí)帽驯,實(shí)現(xiàn)是一樣的龟再,都是調(diào)用attachInfo的Handler對(duì)象,往UI主線程的MessageQueue中扔Runnable尼变,這和直接調(diào)用Handler#post的效果一樣利凑。
當(dāng)attachInfo為空時(shí)浆劲,就得分版本來分析了:
- 7.0及其以上是:往HandlerActionQueue中扔Runnable。具體實(shí)現(xiàn)為:
首先哀澈,調(diào)用getRunQueue()獲得HandlerActionQueue對(duì)象牌借,獲取過程很簡單,看HandlerActionQueue對(duì)象有沒有割按,有就直接返回膨报,沒有就new一個(gè)再返回。
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
然后适荣,調(diào)用HandlerActionQueue對(duì)象的post方法现柠,再調(diào)postDelayed方法(delay 0毫秒),將Runnable對(duì)象封裝成HandlerAction對(duì)象再扔進(jìn)隊(duì)列里:
public class HandlerActionQueue {
private HandlerAction[] mActions;
private int mCount;
public void post(Runnable action) {
postDelayed(action, 0);
}
public void postDelayed(Runnable action, long delayMillis) {
// 將Runnable封裝成HandlerAction對(duì)象
final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
synchronized (this) {
if (mActions == null) {
mActions = new HandlerAction[4];
}
mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
mCount++;
}
}
//省略其他代碼
......
}
- 7.0以下是:通過ViewRootImpl獲取到的RunQueue弛矛,然后往這個(gè)隊(duì)列中扔Runnable够吩。具體實(shí)現(xiàn)為:
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
//省略代碼
...
static final ThreadLocal<RunQueue> sRunQueues = new ThreadLocal<RunQueue>();
//省略代碼
...
static RunQueue getRunQueue() {
RunQueue rq = sRunQueues.get();
if (rq != null) {
return rq;
}
rq = new RunQueue();
sRunQueues.set(rq);
return rq;
}
//省略代碼
...
}
可以發(fā)現(xiàn)兩個(gè)版本主要的不同點(diǎn)在隊(duì)列對(duì)象上。
7.0以下的版本丈氓,隊(duì)列是從一個(gè)ThreadLocal對(duì)象里面獲取到的周循,也就是說,不同的線程會(huì)獲取到不同的RunQueue隊(duì)列對(duì)象万俗,且在同一個(gè)線程里湾笛,隊(duì)列對(duì)象是共享的,大家用一個(gè)该编。
而7.0及以上版本迄本,不管在什么線程里,獲取的都是同一個(gè)HandlerActionQueue隊(duì)列對(duì)象课竣,且隊(duì)列對(duì)象是屬于View實(shí)例自己的,自己用自己的置媳,不共享于樟。
再回顧下,AttachInfo為空時(shí)拇囊,會(huì)先將Runnable緩存在一個(gè)隊(duì)列(隊(duì)列對(duì)象在Android7.0版本前后不同)里面迂曲,那么什么時(shí)候把緩存在隊(duì)列中的Runnable取出來呢?
答案在ViewRootImpl#performTraversals方法中寥袭。
- 7.0及以上:調(diào)用了host的dispatchAttachedToWindow方法路捧,這個(gè)host就是DecorView對(duì)象。
private void performTraversals() {
//省略代碼
...
if (mFirst) {
//省略代碼
...
host.dispatchAttachedToWindow(mAttachInfo, 0);
//省略代碼
...
} else {
//省略代碼
...
}
//省略代碼
...
}
在DecorView和其父類FrameLayout中都沒有重寫dispatchAttachedToWindow方法传黄,于是直接去ViewGroup中找dispatchAttachedToWindow的方法實(shí)現(xiàn):
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
super.dispatchAttachedToWindow(info, visibility); //首先杰扫,調(diào)View的dispatchAttachedToWindow,即通知自身AttachedToWindow
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())); //然后膘掰,調(diào)子View的dispatchAttachedToWindow章姓,通知子View AttachedToWindow
}
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到子View,通過深度優(yōu)先遍歷凡伊,將AttachToWindow這個(gè)事兒傳遍View樹零渐。
在View的dispatchAttachedToWindow中,終于將緩存在RunQueue中的Runnable取出來了系忙,通過AttachInfo的Handler扔到UI線程的MessageQueue中诵盼,等待被執(zhí)行。
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
//省略代碼
...
// Transfer all pending runnables.
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
//省略代碼
...
}
- 7.0以下银还,通過之前的分析我們知道风宁,那個(gè)緩存隊(duì)列是通過ViewRootImpl的一個(gè)靜態(tài)方法getRunQueue()去獲取的。這里同樣調(diào)用getRunQueue()方法獲取對(duì)應(yīng)線程的緩存隊(duì)列见剩,然后調(diào)用executeActions()方法將緩存在隊(duì)列中的Runnable取出來杀糯,最后同樣是通過AttachInfo的Handler往主線程的MessageQueue中扔:
private void performTraversals() {
//省略代碼
...
// Execute enqueued actions on every traversal in case a detached view enqueued an action
getRunQueue().executeActions(mAttachInfo.mHandler);
基于AttachInfo為空這個(gè)大前提下,值得注意的一點(diǎn)是苍苞,如果是在子線程調(diào)用的View#post固翰,那么Runnable是往子線程的緩存隊(duì)列里扔的,而ViewRootImpl#performTraversals是在主線程中被執(zhí)行的羹呵,
getRunQueue()取的是主線程對(duì)應(yīng)的緩存隊(duì)列骂际,這是兩個(gè)不同的緩存隊(duì)列實(shí)例,因此Runnable當(dāng)然不會(huì)被執(zhí)行了冈欢。
總結(jié):
-
如果view已經(jīng)attach到window了歉铝,那么View#post和Handler#post作用一樣,都是往調(diào)用UI主線程的MessageQueue中扔Runnable凑耻。
-
如果view還未attach到window中太示,則需要通過一個(gè)緩存隊(duì)列將Runnable暫時(shí)先緩存起來,等到view attach到window上之后香浩,再將緩存隊(duì)列中的Runnable取出來类缤,再扔到UI線程的MessageQueue中,此時(shí):
- 如果是在主線程調(diào)用的View#post邻吭,那么在各個(gè)Android版本上都沒問題餐弱,都會(huì)在ViewRootImpl的下一次performTraversal()是被執(zhí)行;
- 如果是在非主線程調(diào)用的View#post囱晴,就得分版本考慮:
1)如果是在Android7.0及以上版本膏蚓,沒毛病。緩存隊(duì)列用的是各個(gè)View對(duì)象實(shí)例自己的畸写,跟線程沒關(guān)系驮瞧,通過ViewRootImpl#performTraversal方法,最后會(huì)把各個(gè)View的緩存隊(duì)列中的Runnable都扔到主線程消息隊(duì)列MessageQueue中艺糜,等待被執(zhí)行剧董。
2)如果是在Android7.0以下版本幢尚,就悲劇了。緩存隊(duì)列是從ViewRootImpl中的一個(gè)ThreadLocal<RunQueue>靜態(tài)變量里獲取的翅楼,各個(gè)線程取到的是不同的RunQueue對(duì)象實(shí)例尉剩,子線程是往子線程自己的RunQueue隊(duì)列中扔Runnable,ViewRootImpl#performTraversal是在主線程中被執(zhí)行毅臊,取的是主線程的RunQueue隊(duì)列理茎,于是你在子線程post的Runnable就沒法被執(zhí)行了。
reference: 兩者區(qū)別