由线上问题引发的思考——View.post到底何时执行

前言

今天遇到一个很莫名其妙的问题,就是一个view有两个状态,状态A与状态B,在Activity的onCreate的时候先显示状态A,并在onCreate里面请求接口,接口回来后显示B。
断点的时候,这个逻辑没问题,但是放开断点,很大几率显示的是view的状态A,原因就在于View.post()执行的时机不定,今天这篇文章就来追溯一下View.post()执行的时机。

使用场景

相信大家都知道,我们如果想在Activity.onCreate()里面计算宽高,就可以使用View.post()的方法,并且,在没有Handler的时候,我们可以拿某个View代替Handler的功能,使用View.post(),但是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; }

相信大家跟我一样,很容易忽略,原来post是有返回值的,不过这个返回值其实没有意义,注释也说了,一般是队列退出的时候才这样。
从这里可以看到,这里的attachInfo很关键,这个attchInfo是什么东西呢?
其实,在之前的博客隐约提过这个,attachInfo是在ViewRootImpl里面赋值的,ViewRootImpl是个很复杂的类,几乎跟view有关的所有核心代码都在这里面,他也是连接WMS和View的桥梁,是所有View的根View,注意,ViewRootImpl并不是个View,只是View。网上关于ViewRoot的解析文章汗牛充栋,楼主也是看他们的文章,这里就不打算自己写了,只聊聊我们这里的问题。
View中AttchInfo的赋值是在performTraversals里面

//ViewRootImpl performTraversals()

        Rect frame = mWinFrame;
        if (mFirst) {
        	.....省略代码
            host.dispatchAttachedToWindow(mAttachInfo, 0);
            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
            dispatchApplyInsets(host);
            //Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn);

        } else {
	        ...
        }
       //View dispatchOnWindowAttachedChange()
    public void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mAttachInfo = info;
        ...省略
        if (mRunQueue != null) {
        //这里就是消耗的地方
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
    }     
       

但是,ViewRotImpl里面的AttachInfo的却是在初始化里面

public ViewRootImpl(Context context, Display display) {
	mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,context);
}

由此可见,mAttachInfo的赋值,是在perfomTravels里面,而perfomTravels又是在响应onVsync信号的时候才赋值,而监听垂直信号量又是在Activity的生命周期performRusume()里面的wm.addView()里面执行的。由此可见,在onCreate里面执行的post,并不会执行,而是会走getRunQueue().post(action);

    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;
    }

    /**
     * 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;
    }

HandlerActionQueue是什么呢?官方对于这个类的解释已经很清楚了

Class used to enqueue pending work from Views when no Handler is attached.

也就是说,是在View没有被attach的时候,临时存储工作的。


    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];
            }
            //这个地方其实有一个小小的疑惑,那就是GrowingArrayUtils是V7包里面的类,android原生包并没有,这里是怎么调用成功的。又或者GrowingArrayUtils其实存在只是被隐藏了?
            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
            mCount++;
        }
    }

HanderAction就是用来存储Runnable和delay时间的。
而HandlerAction的消耗就是在executeActions,上面已经在代码里面写过,就是在View.dispatchAttachedToWindow()里面


    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;
        }
    }

注意,这里是只是把这个action发送到ViewRootImple的子线程去了!好了,这个问题基本在这里就解决了,那么,上面几个问题答案也就出来了。

Activity.onCreate()里面计算宽高

弄清楚这个问题,我们就需要搞清楚,在dispatchAttachedToWindow之前,view宽高是否已经测量好了。这个问题其实也很简单,留给读者自己去阅读源码。其实在上面已经解释了,执行executeActions其实只是把Runnable发送到ViewRootImple里面去,很明显,再次执行这个action的时候,performTravels已经执行完成了。

View.post()并不一定会执行

这就是因为view不一定会AttachTo到ViewRootImpl上面,如果没有添加到这个ViewRoot里面,显然就不会执行了。并且,前面也分析过,这个执行时间是无法确定的,所以经常出现断点时可以,但是放开断点就有问题的情况。
但是,注意,这里以API 24为分界线,API24之前,并不是加入ViewRoot里面才执行,具体可以参考博客直面底层:你真的了解 View.post() 原理吗?

后记

这个知识点很危险,如果不知道,就经常容易用错,由此可见,平时多看源码,多看博客是多么重要。

你可能感兴趣的:(源码分析,安卓开发,项目注意)