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不执行。