美文网首页
View.post()源码解析

View.post()源码解析

作者: code希必地 | 来源:发表于2020-09-07 21:49 被阅读0次

很多情况下,我们在Activity的onCreate()中调用View.post()去解决View宽高为0的问题,为什么可以这么做呢?下面我们就带着这个问题去看下源码。

源码分析

1、View.post()

先看下View.post()中到底做了什么?

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

代码很简单,如果attachInfo不为空,则会执行attachInfo.mHandler.post(action);,否则执行getRunQueue().post(action);。这里先看下它们分别做了什么。

  • 1、attachInfo.mHandler.post(action);
    它其实就是调用Handler的post(),这个handler从哪来的稍后再看。
  • 2、getRunQueue().post(action)
    getRunQueue()返回的就是HandlerActionQueue的对象,然后调用它的post()。
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++;
        }
    }

从代码中可以看出,其实就是将Runnable和delayMillis封装到HandlerAction中,并缓存了起来。
这里对View.post()做一个简单的总结:
如果mAttachInfo不会空则直接调用attachInfo.mHandler的post方法,否则将Runnbale和delayMillis封装到HandlerAction中并缓存起来(何时执行缓存的Runnable,后面会讲到)。那么问题来了,mAttachInfo是什么时候赋值的呢?

2、mAttachInfo是什么时候赋值的呢?

经搜索发现是在dispatchAttachedToWindow(AttachInfo info, int visibility)中将info赋值给View的成员变量mAttachInfo,在dispatchDetachedFromWindow()将其置成null。

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mAttachInfo = info;
       //省略代码...
        // Transfer all pending runnables.
        if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
        onAttachedToWindow();
    //省略代码...
    }

dispatchAttachedToWindow()方法中除了给mAttachInfo进行赋值,还会调用mRunQueue.executeActions(info.mHandler);

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

这个方法就是取出缓存的HandlerAction,然后调用Handler的postDelayed()去执行缓存的Runnbale。还记得我们在View.post()中当mAttachInfo==null时会将Runnable给缓存起来,就是在这里调用的啦。
那么View的dispatchAttachedToWindow()又是在哪里调用的呢?

3、View的dispatchAttachedToWindow()的调用

熟悉ViewRootmpl的同学肯定都知道,它是在performTraversals()中调用的,这里不做过多的分析,只简单说下流程。

搜狗截图20200907132122.png
我们主要看下ViewRootImpl.scheduleTraversals()
void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

mChoreographer.postCallback()最终会调用postCallbackDelayedInternal()

private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        if (DEBUG_FRAMES) {
            Log.d(TAG, "PostCallback: type=" + callbackType
                    + ", action=" + action + ", token=" + token
                    + ", delayMillis=" + delayMillis);
        }

        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

这里只说结论,就不具体分析了:

  • 1、首先将mTraversalRunnable传给Choreographer,以当前的时间戳放进一个mCallbackQueues队列中等待执行,然后调用native方法nativeScheduleVsync()在底层注册监听下一个屏幕刷新信号的事件。
  • 2、当下一个屏幕刷新的信号发出的时候,由于我们对信号做了监听,所以会回调app层的onVsync。当onVsync回调时,会发一个Message到主线程,将后续的工作切换到主线程中工作,即从mCallbackQueues队列中根据时间戳将之前存入的Runnable取出来执行,则mTraversalRunnable也会被取出来执行。
final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

执行mTraversalRunnable则会执行doTraversal(),在doTraversal()中又会执行performTraversals()

private void performTraversals() {
        // mView就是DecorView,在setView()时进行的赋值
        final View host = mView;

        //省略部分代码....
        
        //默认为true
        if (mFirst) {
            mFullRedrawNeeded = true;
            mLayoutRequested = true;
            //省略部分代码....
            
            //调用DecorView的dispatchAttachedToWindow(),DecorView是FrameLayout,则会调用ViewGroup的
            //dispatchAttachedToWindow(),ViewGroup的dispatchAttachedToWindow()会遍历child调用child的dispatchAttachedToWindow()
            host.dispatchAttachedToWindow(mAttachInfo, 0);
            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
            dispatchApplyInsets(host);
        } else {
            //省略部分代码....
        }
        if(....){
            performMeasure();
        }
        if(....){
            performLayout();
        }
        if(....){
            performDraw();
        }
    }
  • 1、host.dispatchAttachedToWindow(mAttachInfo, 0)会调用View的dispatchAttachedToWindow()并将mAttachInfo传递给View,mAttachInfo实在ViewRootImpl的构造函数中初始化的
  • 2、接下来会对整个View树进行测量、布局、绘制流程。
    有的人可能会有疑问:View的dispatchAttachedToWindow()明明是在测量、布局、绘制之前调用的,为什么View.post()中Runnable执行时能获取到宽高呢?
    其实原因我们上面已经说明了:performTraversals()的执行是在监听到下一个屏幕刷新信号时,会发送一个Message到主线程中进行执行的,熟悉Handler的同学都知道,MessageQueue中的消息只有执行完一个,才会取下一个执行。所以这也就保证了在View.post()中Runnable执行时已经完成了测量、布局、绘制。

相关文章

网友评论

      本文标题:View.post()源码解析

      本文链接:https://www.haomeiwen.com/subject/xeekektx.html