View.post(Runnble)的一点小问题

今天刚好看到一个问题,为什么onCreate() 中使用 View.post(Runnable)可以拿取到View的宽高,第一想法就是内部利用handler将Runnbale加入主线程MessageQueue,执行完测量任务之后再执行Runnable。

点开源码之后发现好像没那么简单,不过也没那么难

public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action);
    }
    getRunQueue().post(action);
    return true;
}

post()方法中,如果有attachInfo,直接attachInfo.mHandler.post(),若没有就getRunQueue().post();

看下第一种情况,attachInfo是什么时候不为null,还有mHandler是哪里的Handler?
// View
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    mAttachInfo = info;
    .... 
    if (mRunQueue != null) {
        mRunQueue.executeActions(info.mHandler);
        mRunQueue = null;
    }
}

mAttachInfo 是在diapatchAttachedToWindow()赋值的,ViewRootImpl的performTraversals()会调用ViewGroup 的 diapatchAttachedToWindow(),最后会调用子View的diapatchAttachedToWindow()

// ViewRootImpl
private void performTraversals() {
	// 如果是首次绘制
	if (mFirst) {
		host.dispatchAttachedToWindow(mAttachInfo, 0);
	}
}

// ViewRootImpl
final ViewRootHandler mHandler = new ViewRootHandler();

// ViewRootImpl
public ViewRootImpl(Context context, Display display) {
	mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this, context);
}

从上面代码可以看出,mAttachInfo在ViewRootImpl构造,并且performTraversals(),若i是首次绘制,会把mAttachInfo赋给View。

View里面的Handler其实就是ViewRootImpl的ViewRootHandler 。

ViewRootHandler直接在主线程构造,所以是消息处理都是在主线程中。

小结:

diapatchAttachedToWindow()后,mAttachInfo不为null,就可以直接通过ViewRootHandler处理Runnable,最后能够获取View宽高。

但是不是 diapatchAttachedToWindow()之前,就已经测量好宽高了呢?


再看下第二种情况,getRunQueue().post(action);
// View.java
private HandlerActionQueue mRunQueue;

private HandlerActionQueue getRunQueue() {
    if (mRunQueue == null) {
        mRunQueue = new HandlerActionQueue();
    }
    return mRunQueue;
}

// HandlerActionQueue 
public class HandlerActionQueue {
    private HandlerAction[] mActions;
    private int 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;
        }
    }
}

这里就直接说结果了,在attachInfo为null的时候,View.post()会将Runnable保存在HandlerActionQueue中,等待dispatchAttachedToWindow() 的时候,mRunQueue.executeActions(info.mHandler) 会执行保存在HandlerActionQueue的Runnbale。

可见,我没有考虑到测量绘制还没开始的情况,如果测量还没开始就需要一个数组将Runnable存起来,在测量结束后执行。


但是都同样存在一个问题:是不是 diapatchAttachedToWindow()之前,就已经测量好宽高了呢?

// ViewRootImpl
private void performTraversals() {
	host.dispatchAttachedToWindow(mAttachInfo, 0);
	
	performMeasure();
	
	performLayout();

	performDraw();
}

上面的伪代码指出,performTraversals()中是先执行dispatchAttachedToWindow(),后面才执行measure()layout()等测量,这个怎么保证post.view()肯定能拿到宽高?

后面终于搞清楚了!

原来无论是perfromTraversals,还是后面的Runnable,都是保存在MessageQueue,Looper会执行完当前的任务,再从MessageQueue取出下个任务接着执行;

第一种情况,attachInfo.mHandler.post(action),和第二种情况,在dispatchAttachedToWindow executeActions(),都是增加任务到在主线程的MessageQueue中,而Looper只会处理完当前的测量任务才会去执行View.post的任务。

可以看下面的示意图,当前还在执行performTraversals(),后续的任务还在MessageQueue等待执行。

View.post(Runnble)的一点小问题_第1张图片

你可能感兴趣的:(android)