View.post(Runnable)調用之後,Runnable不一定會執行,那麼什麼時候不執行呢?下面來分析一下:
交流 之 把事情講清楚的技巧:結論先行,分析隨後。
先給結論:在Android 7.0之前,View還沒有附着到Window,且View.post(Runnable)在子線程執行,那麼這個Runnable就不會執行。
首先看看它的源碼(Android 7.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;
}
從源碼可以看到,如果mAttachInfo存在,就會使用mAttachInfo.mHandler來發送消息,那這樣的話 Runnable一般會執行完成。如果mAttachInfo不存在的話,就會走getRunQueue().post()方法,這個getRunQueue其實就是保存了一個Action集合,在需要的時候取出裏面的Runnable再執行。
那麼我們看看什麼時候給mAttachInfo賦值的:
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方法時,會給mAttachInfo賦值,在dispatchAttachedToWindow裏面會調用onAttachedToWindow()方法,也就是說如果View附着到Window上,mAttachInfo就存在。
那什麼時候去執行getRunQueue()裏面保存的Runnable呢?細心的同學應該看到了,在dispatchAttachedToWindow裏面有一段:
// Transfer all pending runnables.
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
傳遞待定的Runnables,也就是說如果View還沒有附着到Window,mAttachInfo就不存在,Runnable就會存儲到mRunQueue裏面,等到View需要附着到Window後(dispatchAttachedToWindow調用時),就會一一執行。不信?且看:
View.java
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
HandlerActionQueue.java
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) {
//初始容量是4
mActions = new HandlerAction[4];
}
//這裏是擴容操作,擴容:x2
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;
}
}
分析到這裏,大家可以看到最終只要View附着到Window,那麼Runnable還是會執行。好的,別急,上面看到的代碼是Android 7.0或以上的代碼。
現在咱們來看一下Android 7.0之前的代碼,以Android 5.0爲例:
先看一下View.post(Runnable)源碼:
public boolean post(Runnable action) {
Handler handler;
if (mAttachInfo != null) {
handler = mAttachInfo.mHandler;
} else {
// Assume that post will succeed later
ViewRoot.getRunQueue().post(action);
return true;
}
return handler.post(action);
}
這裏與Android 7.0的區別在於:
ViewRoot.getRunQueue().post(action);
點進去看看是怎麼實現:
static RunQueue getRunQueue() {
RunQueue rq = sRunQueues.get();
if (rq != null) {
return rq;
}
rq = new RunQueue();
sRunQueues.set(rq);
return rq;
}
看看sRunQueues是什麼數據結構:
static final ThreadLocal<RunQueue> sRunQueues = new ThreadLocal<RunQueue>();
這裏RunQueue是用ThreadLocal裝的,ThreadLocal是用ThreadLocalMap實現的,而ThreadLocalMap是以key--value存儲的,是用Thread來作爲key值的。
再看看ViewRoot.getRunQueue()什麼時候用的:
ViewRoot.java
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
...
if (mFirst) {
...
host.dispatchAttachedToWindow(attachInfo, 0);
//Log.i(TAG, "Screen on initialized: " + attachInfo.mKeepScreenOn);
} else {
desiredWindowWidth = frame.width();
desiredWindowHeight = frame.height();
if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
if (DEBUG_ORIENTATION) Log.v(TAG,
"View " + host + " resized to: " + frame);
fullRedrawNeeded = true;
mLayoutRequested = true;
windowResizesToFitContent = true;
}
}
if (viewVisibilityChanged) {
attachInfo.mWindowVisibility = viewVisibility;
host.dispatchWindowVisibilityChanged(viewVisibility);
if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) {
if (mUseGL) {
destroyGL();
}
}
if (viewVisibility == View.GONE) {
// After making a window gone, we will count it as being
// shown for the first time the next time it gets focus.
mHasHadWindowFocus = false;
}
}
boolean insetsChanged = false;
if (mLayoutRequested) {
// Execute enqueued actions on every layout in case a view that was detached
// enqueued an action after being detached
getRunQueue().executeActions(attachInfo.mHandler);
...
}
...
}
可以看到View.dispatchAttachedToWindow和ViewRoot.getRunQueue()都在這個方法裏面調用了,那也就是說ViewRoot.getRunQueue()會在View附着到Window上的時候調用,然後根據當前線程取出用於存儲Runnable的集合。到這裏就恍然大悟了,ViewRoot.performTraversals是在UI線程執行的,那麼ViewRoot.getRunQueue()裏面除了主線程保存的Runnable,其他的都不會得到執行。因此可以得出結論:Android 7.0以下,View沒有附着到Window時,在子線程調用View.post(Runnable)方法,會導致Runnable不執行。