美文网首页
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