为什么View.post中能获取到View宽高

众所周知,Activity中onCreate、onResume中无法直接获取到View的宽高,原因是在这些生命周期中,View还没有经过measure流程,为什么通过View.post方法能在回调中获取到View的宽高呢?

接下来我们就扒一扒View.post的源码,看看为什么在View.post中能正常打印View的宽高。

一、 Runnable去哪了?

先跟踪View.post方法,看看这个post出去的Runnable去哪了。

    // View.post
    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;
    }
    // AttachInfo构造方法
    AttachInfo(IWindowSession session, IWindow window, Display display,
                ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer,
                Context context) {
            mSession = session;
            mWindow = window;
            mWindowToken = window.asBinder();
            mDisplay = display;
            mViewRootImpl = viewRootImpl;
            mHandler = handler;
            mRootCallbacks = effectPlayer;
            mTreeObserver = new ViewTreeObserver(context);
        }

可以看到,View.post中会先尝试调用mAttachInfo的handler处理Runnable,如果mAttachInfo还没有初始化,会将其加入到一个队列里。
而AttachInfo的handler是一个普通的handler,如果调用它的post方法处理Runnable,是做不到确保View测绘完才执行Runnable的。由此可以断定Activity onCreate和onResume时 View的mAttachInfo没有完成初始化,post出去的Runnable是被加入了一个队列,当然也可以使用反射打印mAttachInfo或打印View.getViewRootImpl的结果验证这个推断。

二、 给Runnable排排队

跟踪getRunQueue()代码我们很容易得知,这个队列的类是HandlerActionQueue,代码如下(有删减)

public class HandlerActionQueue {
    private HandlerAction[] mActions;
    private int mCount;

    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 boolean matches(Runnable otherAction) {
            return otherAction == null && action == null
                    || action != null && action.equals(otherAction);
        }
    }
}

被post进来的Runnable会被封装为HandlerAction对象,并被维护进一个HandlerAction数组中,等调用executeActions(Hander handler)方法时,会依次将HandlerAction取出,将给传入的handler执行。

三、什么时候执行Runnable呢?

上面分析到执行Runnable的时机就是触发executeActions的时机,在View中搜索executeActions,只有一处调用,在View.dispatchAttachedToWindow()方法中。

    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();

        ListenerInfo li = mListenerInfo;
        final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
                li != null ? li.mOnAttachStateChangeListeners : null;
        if (listeners != null && listeners.size() > 0) {
            for (OnAttachStateChangeListener listener : listeners) {
                listener.onViewAttachedToWindow(this);
            }
        }
        ...
        int vis = info.mWindowVisibility;
        if (vis != GONE) {
            onWindowVisibilityChanged(vis);
            if (isShown()) {
                onVisibilityAggregated(vis == VISIBLE);
            }
        }
        ...
        onVisibilityChanged(this, visibility);
        ...
    }

一看名字就知道这是一个很重要的方法,View在这里给mAttachInfo赋了值(mAttachInfo携带了很多跟Window、ViewRootImpl相关的信息,并且以后再调用View.post都会交给mAttachInfo处理),执行了mAttachInfo没赋值期间post进来的Runnable等等。
当然,找到了Runnable调用的地方才是刚开始。
接着我们需要知道View.dispatchAttachedToWindow()是什么时候调用的,跟View.measure()有什么明确的时序关系。惭愧呀惭愧。

四、dispatchAttachedToWindow执行时机

有Android源码的同学可以在framework/base下grep一下,没有的可以上http://androidxref.com/搜索一下dispatchAttachedToWindow方法,很容易可以定位到这个方法是在ViewRootImpl.performTraversals()里执行的,performTraversals应该是大家比较眼熟的一个方法了,可以简化成下面的样子

    performTraversals() {
        if(isFirst) {
            host.dispatchAttachedToWindow(mAttachInfo, 0);
        }
        performMeasure();
        performLayout()
        performDraw();
    }

由(三)的分析可以知道执行dispatchAttachedToWindow的时候将post消息传递了出去,也就是说View.post的消息一定是在performTraversals执行完才执行的。


想起来在面试的时候回答了是因为Choreographer收到SF发来的Vsync信号触发绘制时会先postSyncBarrier,导致同步message被阻塞。。。真是牛头不对马嘴呀。。。
Choreographer触发绘制时确实会先postSyncBarrier,优先处理绘制消息,但在执行绘制消息的doTraversals方法里已经把Barrier移除了,而performTraversals正是在Barrier移除后执行的。

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
            performTraversals();
            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

另外,在performTraversals中的这段代码解答了我对多次measure的疑惑,如果LayoutParam使用了weight,将会以精确模式再次触发measure流程。

    if (lp.horizontalWeight > 0.0f) {
        width += (int) ((mWidth - width) * lp.horizontalWeight);
        childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
                MeasureSpec.EXACTLY);
        measureAgain = true;
    }
    if (lp.verticalWeight > 0.0f) {
        height += (int) ((mHeight - height) * lp.verticalWeight);
        childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
              MeasureSpec.EXACTLY);
        measureAgain = true;
   }

   if (measureAgain) {
       if (DEBUG_LAYOUT) Log.v(mTag,
                  "And hey let's measure once more: width=" + width
                  + " height=" + height);
       performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
   }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章