探究View.post获取View宽高问题

1 提出问题:

执行View.post()的时候,此时View是开始被measure?还是在measure之前执行?如果在measure之前执行,

而post又没有进行延时,那么这个runnable又是如何被放到测量之后进行的呢?

首先跟踪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);         ①
        }
        // Assume that post will succeed later
        ViewRootImpl.getRunQueue().post(action);        ②
        return true;
    }

分析:

如果mAttachInfo不为Null的时候,会执行1;否则执行2;

所以现在需要知道View中mAttachInfo是否为空,

View中能给mAttachInfo赋值的地方只有一处,在dispatchAttachedToWindow()方法里赋值。

/**
     * @param info the {@link android.view.View.AttachInfo} to associated with
     *        this view
     */
    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        //system.out.println("Attached! " + this);
        mAttachInfo = info;        ①
        if (mOverlay != null) {
            mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
        }
        mWindowAttachCount++;
        // We will need to evaluate the drawable state at least once.
        mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
        if (mFloatingTreeObserver != null) {
            info.mTreeObserver.merge(mFloatingTreeObserver);
            mFloatingTreeObserver = null;
        }
        if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {
            mAttachInfo.mScrollContainers.add(this);
            mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
        }
        performCollectViewAttributes(mAttachInfo, visibility);
        onAttachedToWindow();        ② 

        ListenerInfo li = mListenerInfo;
        final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
                li != null ? li.mOnAttachStateChangeListeners : null;
        if (listeners != null && listeners.size() > 0) {
            // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
            // perform the dispatching. The iterator is a safe guard against listeners that
            // could mutate the list by calling the various add/remove methods. This prevents
            // the array from being modified while we iterate it.
            for (OnAttachStateChangeListener listener : listeners) {
                listener.onViewAttachedToWindow(this);
            }
        }

        int vis = info.mWindowVisibility;
        if (vis != GONE) {
            onWindowVisibilityChanged(vis);
        }
        if ((mPrivateFlags&PFLAG_DRAWABLE_STATE_DIRTY) != 0) {
            // If nobody has evaluated the drawable state yet, then do it now.
            refreshDrawableState();
        }
        needGlobalAttributesupdate(false);
    }

可以分析知道onCrate方法先于onAttached执行,而赋值在onAttached之前,那么onCreate的时候mAttachInfo为空。

这个时候会执行ViewRootImpl.getRunQueue.post()这行代码。

所以在onCreate执行view.post的方法时,那些Runnable并没有马上被执行,而是保存到RunQueue中。那么它在什么什么时候执行,

static RunQueue getRunQueue() {
        RunQueue rq = sRunQueues.get();
        if (rq != null) {
            return rq;
        }
        rq = new RunQueue();
        sRunQueues.set(rq);
        return rq;
    }

下面继续分析:

执行的接口就是:RunQueue.executeActions(),其内部也是调用handler.RunQueue.executeActions()这个接口:

/**
     * The run queue is used to enqueue pending work from Views when no Handler is
     * attached.  The work is executed during the next call to performTraversals on
     * the thread.
     * @hide
     */
    static final class RunQueue {
        private final ArrayList<HandlerAction> mActions = new ArrayList<HandlerAction>();

        void post(Runnable action) {
            postDelayed(action, 0);
        }

        void postDelayed(Runnable action, long delayMillis) {
            HandlerAction handlerAction = new HandlerAction();
            handlerAction.action = action;
            handlerAction.delay = delayMillis;

            synchronized (mActions) {
                mActions.add(handlerAction);
            }
        }

        void removeCallbacks(Runnable action) {
            final HandlerAction handlerAction = new HandlerAction();
            handlerAction.action = action;

            synchronized (mActions) {
                final ArrayList<HandlerAction> actions = mActions;

                while (actions.remove(handlerAction)) {
                    // Keep going
                }
            }
        }

        void executeActions(Handler handler) {
            synchronized (mActions) {
                final ArrayList<HandlerAction> actions = mActions;
                final int count = actions.size();

                for (int i = 0; i < count; i++) {
                    final HandlerAction handlerAction = actions.get(i);
                    handler.postDelayed(handlerAction.action, handlerAction.delay);
                }

                actions.clear();
            }
        }

        private static class HandlerAction {
            Runnable action;
            long delay;

            @Override
            public boolean equals(Object o) {
                if (this == o) return true;
                if (o == null || getClass() != o.getClass()) return false;

                HandlerAction that = (HandlerAction) o;
                return !(action != null ? !action.equals(that.action) : that.action != null);

            }

            @Override
            public int hashCode() {
                int result = action != null ? action.hashCode() : 0;
                result = 31 * result + (int) (delay ^ (delay >>> 32));
                return result;
            }
        }
    }

在ViewRootImpl里只有一个地方调用;就是performTraversals():

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

    // 这里面做了一些初始化的操作,第一次执行和后面执行的操作不一样,这里不关
    // 心过多的东西,主要关心attachInfo在此处被初始化完成

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

    ...
    performMeasure();
    ...
    performLayout();
    ...
    performDraw();
 }

performTraversals()相信大家都听过,它非常重要,它的作用就是遍历整个View树,并且按照要求进行measure,layout和draw流程,

刚才post的Runnable就是在这里执行了,但是表面上看起来是先执行Runnable,然后执行measure,layout和draw。那么这样它又如何

是怎么获取测量的值呢?

我们仔细分析executeActions()。其实它只是调用handler的post一个Runnable方法,而且这个handler是ViewRootImpl的。所以会

在主线程执行这个Runnable.

这里引入其他知识点,Android的运行其实是消息驱动模式,程序在启动的时候会在ActivityThread的

main方法

 public static void main(String[] args) {
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        Security.addProvider(new AndroidKeyStoreProvider());

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        AsyncTask.init();

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

进行创建主线程的Handler和Looper,并且最后调用loop()方法进入循环,它一直在等待消息驱动它执行下去。

而像我们post的Runnable就是消息的一种了。

所以此时需要知道主线程执行的任务,然后才会去执行我们post的Runnable.

那么此时正在执行什么任务呢?正如上面所说:Android是消息驱动模式,那么在执行performTraversals()又是什么消息在驱动呢?

而这个消息正是TraversalRunnable,

因此这个时候hanlder正在执行TraversalRunnable,而我们post的Runnable需要TraversalRunnable执行完后执行

TraversalRunnable这里面会进行measure,layout和draw流程,所以等到执行我们的Runnable。此时View的宽高就可以获取了。


2 这里提出一个疑问?

如果在onCreate里新建一个线程post,那么这个Runnable很可能就没有执行了。

注释只是说任务会在主线程中执行,并没有有什么友好的提示:

关于这个bug:

其实并不是不能在子线程中调用view.post,相反,这个方法的作用就是可以让我们把子线程任务放到主线程执行。

但是有个前提条件:要在View.onAttachToWindow执行,如果在View.post方法执行的话,那么这个Runnable讲不会得到执行。

原因分析:

View.onAttachToWindow之后调用,那么mAttachInfo就不为空,那么早post方法就会返回attachInfo.mHandler.post(action);

 public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
        // Assume that post will succeed later
        ViewRootImpl.getRunQueue().post(action);
        return true;
    }

4.4源码有这个方法解释:

This method can be invoked from outside of the UI thread only when this View is attached to a window.








  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值