为何在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的方法中:

/**
 * 

Causes the Runnable to be added to the message queue. * The runnable will be run on the user interface thread.

* * @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能获取宽高值。

你可能感兴趣的:(Android,源码学习)