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

来源:互联网 发布:张晓东 大数据 编辑:程序博客网 时间:2024/06/07 16:46

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
原创粉丝点击