为何在onCreate中通过View.post能获取宽高

Posted yuminfeng728

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为何在onCreate中通过View.post能获取宽高相关的知识,希望对你有一定的参考价值。

我们在获取View的宽高时,其实执行的代码是:

/**
 * Return the width of the your view.
 *
 * @return The width of your view, in pixels.
 */
@ViewDebug.ExportedProperty(category = "layout")
public final int getWidth() 
    return mRight - mLeft;


/**
 * Return the height of your view.
 *
 * @return The height of your view, in pixels.
 */
@ViewDebug.ExportedProperty(category = "layout")
public final int getHeight() 
    return mBottom - mTop;

而我们知道 mRight,mLeft,mTop,mBottom 四个值是在View执行完layout 方法时才会被赋值,而layout方法是由ViewRootImpl中performLayout发起调用的。但是我们也知道ViewRootImpl调用performLayout方法是在Activity执行完onResume方法之后才开始执行的。这里就出现了题目中提出的View.post是如何使得在onCreate方法中也能获得View的宽高。

我们首先进入View.post的方法中:

/**
 * <p>Causes the Runnable to be added to the message queue.
 * The runnable will be run on the user interface thread.</p>
 *
 * @param action The Runnable that will be executed.
 *
 * @return Returns true if the Runnable was successfully placed in to the
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.
 *
 * @see #postDelayed
 * @see #removeCallbacks
 */
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;

注释中已经说明,将一个Runnable加入到消息队列中来,并且runnable将在用户界面线程上运行。方法中首先对attachInfo 判断是否为空,这里我们之前分析过,知道mAttachInfo对象其实是在dispatchAttachedToWindow方法中初始化的,就在ViewRootImpl的performTraversals方法中执行。这里直接执行下面getRunQueue().post(action)。

/**
 * Returns the queue of runnable for this view.
 *
 * @return the queue of runnables for this view
 */
private HandlerActionQueue getRunQueue() 
    if (mRunQueue == null) 
        mRunQueue = new HandlerActionQueue();
    
    return mRunQueue;

如果为空,则为View实例化一个runnable队列。我们可以分析一下这个类:

HandlerActionQueue

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

    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) 
                mActions = new HandlerAction[4];
            
            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
            mCount++;
        
    

    public void removeCallbacks(Runnable action) 
        synchronized (this) 
            final int count = mCount;
            int j = 0;

            final HandlerAction[] actions = mActions;
            for (int i = 0; i < count; i++) 
                if (actions[i].matches(action)) 
                    // Remove this action by overwriting it within
                    // this loop or nulling it out later.
                    continue;
                

                if (j != i) 
                    // At least one previous entry was removed, so
                    // this one needs to move to the "new" list.
                    actions[j] = actions[i];
                

                j++;
            

            // The "new" list only has j entries.
            mCount = j;

            // Null out any remaining entries.
            for (; j < count; j++) 
                actions[j] = null;
            
        
    

    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;
        
    

    public int size() 
        return mCount;
    

    public Runnable getRunnable(int index) 
        if (index >= mCount) 
            throw new IndexOutOfBoundsException();
        
        return mActions[index].action;
    

    public long getDelay(int index) 
        if (index >= mCount) 
            throw new IndexOutOfBoundsException();
        
        return mActions[index].delay;
    

    private static class HandlerAction 
        final Runnable action;
        final long delay;

        public HandlerAction(Runnable action, long delay) 
            this.action = action;
            this.delay = delay;
        

        public boolean matches(Runnable otherAction) 
            return otherAction == null && action == null
                    || action != null && action.equals(otherAction);
        
    

post里面执行的postDelayed方法,然后创建一个大小为4的数组,并且将保存Runnable对象的HandlerAction对象存储到数组中。
到这里一个Runnable任务就保存了起来,但是这里并没有立即执行Runnable任务,那么到底在哪里执行的呢?
如果对前面分析界面绘制的文章有印象的话,应该知道是在ViewRootImpl中performTraversals执行的:

ViewRootImpl#performTraversals

private void performTraversals() 
        // cache mView since it is used so much below...
        final View host = mView;

        if (host == null || !mAdded)
            return;

        .....

        // Execute enqueued actions on every traversal in case a detached view enqueued an action
        getRunQueue().executeActions(mAttachInfo.mHandler);

        .....    
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

        .....            
        performLayout(lp, mWidth, mHeight);

        .....
        performDraw();

    

这里我们看到了执行executeActions方法,但是它竟然先于performMeasure方法执行,这下心中一紧,如果这样的话那么通过View.post()方式获取的应该是还没有测量过的宽高啊,应该没有值才对。我们这里先保留这个问题。
先看HandlerActionQueue中的executeActions方法:

HandlerActionQueue#executeActions

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;
    

遍历数组中存储的Runnable任务,然后通过handler来执行,由handler来post这个Runnable对象,于是这个Runnable任务又被加入到Handler中的MessageQueue中。我们知道ViewRootImpl的Handler就是主线程的Handler,而performTraversals()所在的Runnable最后会被添加到同一个主线程中的looper的MessageQueue中。由于是Handler中的消息驱动模式,需要等待主线程的Handler执行完当前的任务(即performTraversals),才会去执行我们View.post的那个Runnable。如此才避免了executeActions中的runnable在performLayout方法之前调用。

到这里,我们便解释了为何在onCreate中通过View.post能获取宽高值。

以上是关于为何在onCreate中通过View.post能获取宽高的主要内容,如果未能解决你的问题,请参考以下文章

朝8晚5用“笨”方法的他,为何54岁就得了诺奖?

setOnFocusChangeListener的使用

IntentService源码分析

一个ViewModel,多个Activity

在C中通过引用传递数组?

在 C++ 中通过引用传递对象