美文网首页
view.post()

view.post()

作者: 风月寒 | 来源:发表于2021-03-02 10:43 被阅读0次

    我们都知道,如果直接在onCreate()去获取一个控件的宽高,是获取不到的,需要在onCreate()里面直接调用view.post()或者view.postDelayed去获取。

    btn.post(new Runnable() {
        @Override
        public void run() {
            btn.getWidth();
        }
    });
    btn.postDelayed(new Runnable() {
        @Override
        public void run() {
                    
        }
    },100);
    

    下面我们来具体看看post和postDelayed具体怎么操作。

    public boolean post(Runnable action) {
            final AttachInfo attachInfo = mAttachInfo;
            if (attachInfo != null) {
                return attachInfo.mHandler.post(action);
            }
    
            getRunQueue().post(action);
            return true;
        }
    

    在post的方法中分两种情况,如果AttachInfo不为null,则直接调用handler的post(),否则调用HandlerActionQueue的post()。一开始进来AttachInfo是为null的,那就会走下面的逻辑。

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

    HandlerActionQueue的post()的将传过来的runnable封装成一个HandlerAction放在HandlerAction数组中。好像从这里并不能发现什么,那回过头去看AttachInfo什么时候不为null,也就是什么时候赋值。全局搜索可知,在dispatchAttachedToWindow()进行赋值。

    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
            mAttachInfo = info;
            if (mOverlay != null) {
                mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
            }
            mWindowAttachCount++;
            
            if (mRunQueue != null) {
                mRunQueue.executeActions(info.mHandler);
                mRunQueue = null;
            }
            performCollectViewAttributes(mAttachInfo, visibility);
            onAttachedToWindow();
            ....
        }
    

    从上面可以知道,赋值完成之后,然后判断mRunQueue是否为null,如果不为null,则调用executeActions()。

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

    将包装好的Runnable放到mAttachInfo中的handler中。而mAttachInfo中的handler是在ViewRootImpl的构造函数中进行赋值,

    final ViewRootHandler mHandler = new ViewRootHandler();
    
    

    那什么时候会调用这个方法?在哪里调用?接下来看AttachInfo.全局搜索是在ViewRootImpl里面调用,在ViewRootImpl的构造函数里会创建一个mAttachInfo对象,然后在performTraversals()调用
    dispatchAttachedToWindow()。我们都知道,在 ViewRootImpl 的 performTraversals 方法,在该方法将会依次完成 View 绘制流程的三大阶段:测量、布局和绘制。

    // View 绘制流程开始在 ViewRootImpl
    private void performTraversals() {
        // mView是DecorView
        final View host = mView;
        if (mFirst) {
            .....
            // host为DecorView
            // 调用DecorVIew 的 dispatchAttachedToWindow,并且把 mAttachInfo 给子view
            host.dispatchAttachedToWindow(mAttachInfo, 0);
            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
            dispatchApplyInsets(host);
            .....
        }
       mFirst=false
       ...
       
       getRunQueue().executeActions(mAttachInfo.mHandler);
       // View 绘制流程的测量阶段
       performMeasure();
       // View 绘制流程的布局阶段
       performLayout();
       // View 绘制流程的绘制阶段
       performDraw();
       ...
    
    }
    

    从上面可知,dispatchAttachedToWindow是在performMeasure()的前面执行,它又是怎么保证在测量后才得到器宽高的。

    那我们得看view的绘制流程。在调用performTraversals的前面会调用scheduleTraversals()。

    void scheduleTraversals() {
            if (!mTraversalScheduled) {
                mTraversalScheduled = true;
                mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
                mChoreographer.postCallback(
                        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
                if (!mUnbufferedInputDispatch) {
                    scheduleConsumeBatchedInput();
                }
                notifyRendererOfFramePending();
                pokeDrawLockIfNeeded();
            }
        }
    

    在scheduleTraversals()会添加一个mTraversalBarrier,这个作用就是添加一个栅栏,让异步的消息优先执行,而同步消息挡在栅栏外面。
    这时会调用Choreographer的postCallback(),并将mTraversalRunnable作为参数传进去。而mTraversalRunnable是后续的View的绘制任务。

    下面来看一下postCallback(),

    public void postCallback(int callbackType, Runnable action, Object token) {
            postCallbackDelayed(callbackType, action, token, 0);
        }
        
    public void postCallbackDelayed(int callbackType,
                Runnable action, Object token, long delayMillis) {
            if (action == null) {
                throw new IllegalArgumentException("action must not be null");
            }
            if (callbackType < 0 || callbackType > CALLBACK_LAST) {
                throw new IllegalArgumentException("callbackType is invalid");
            }
    
            postCallbackDelayedInternal(callbackType, action, token, delayMillis);
        }
    
    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);
                }
            }
        }
    

    postCallback最终调用的是postCallbackDelayedInternal(),特别注意一下postCallback方法的第三个参数,后面需要根据这个token做判断。

    在postCallbackDelayedInternal根据时间来判断如果延时小于now,则执行scheduleFrameLocked(),否则设置成异步任务,然后通过handler发送到队列当中去。

    private void scheduleFrameLocked(long now) {
            if (!mFrameScheduled) {
                mFrameScheduled = true;
                if (USE_VSYNC) {
                    if (DEBUG_FRAMES) {
                        Log.d(TAG, "Scheduling next frame on vsync.");
                    }
    
                    if (isRunningOnLooperThreadLocked()) {
                        scheduleVsyncLocked();
                    } else {
                        Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                        msg.setAsynchronous(true);
                        mHandler.sendMessageAtFrontOfQueue(msg);
                    }
                } else {
                    final long nextFrameTime = Math.max(
                            mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
                    Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtTime(msg, nextFrameTime);
                }
            }
        }
    

    先判断当前looper是不是同一个,是同一个则调用scheduleVsyncLocked()。否则设置成一个异步消息。

    private void scheduleVsyncLocked() {
            mDisplayEventReceiver.scheduleVsync();
        }
        
    public void scheduleVsync() {
            if (mReceiverPtr == 0) {
                Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
                        + "receiver has already been disposed.");
            } else {
                nativeScheduleVsync(mReceiverPtr);
            }
        }
        
    private static native void nativeScheduleVsync(long receiverPtr);
    

    最终调用native方法去请求Vsync信号。得到Vsync信号成功之后,会回调onVsync()。

    public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
                long now = System.nanoTime();
                if (timestampNanos > now) {
                    timestampNanos = now;
                }
    
                if (mHavePendingVsync) {
    
                } else {
                    mHavePendingVsync = true;
                }
    
                mTimestampNanos = timestampNanos;
                mFrame = frame;
                Message msg = Message.obtain(mHandler, this);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
            }
            
            @Override
            public void run() {
                mHavePendingVsync = false;
                doFrame(mTimestampNanos, mFrame);
            }
    

    通过Handler发送了一条消息,执行了本身的Runnable回调方法,也就是doFrame()。

    void doFrame(long frameTimeNanos, int frame) {
            final long startNanos;
            synchronized (mLock) {
                long intendedFrameTimeNanos = frameTimeNanos;
                startNanos = System.nanoTime();
                final long jitterNanos = startNanos - frameTimeNanos;
                if (jitterNanos >= mFrameIntervalNanos) {
                    final long skippedFrames = jitterNanos / mFrameIntervalNanos;
                    if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
                        
                    }
                    final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
                    
                    }
                    frameTimeNanos = startNanos - lastFrameOffset;
                }
    
                if (frameTimeNanos < mLastFrameTimeNanos) {
                    scheduleVsyncLocked();
                    return;
                }
    
                if (mFPSDivisor > 1) {
                    long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
                    if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
                        scheduleVsyncLocked();
                        return;
                    }
                }
    
                mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
                mFrameScheduled = false;
                mLastFrameTimeNanos = frameTimeNanos;
            }
    
            try {
                Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
                AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
    
                mFrameInfo.markInputHandlingStart();
                doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
    
                mFrameInfo.markAnimationsStart();
                doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
                doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
    
                mFrameInfo.markPerformTraversalsStart();
                doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
    
                doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
            } finally {
                AnimationUtils.unlockAnimationClock();
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
        }
    

    上面的代码做了两件事:

    1、开始绘制要在vsync信号来的时候开始,保证两者时间对应。所以如果时间没对上,就是发送了跳帧,那么就要修正这个时间,保证后续的时间对应上。

    2、执行所有的Callback任务。

    void doCallbacks(int callbackType, long frameTimeNanos) {
            CallbackRecord callbacks;
            synchronized (mLock) {
                final long now = System.nanoTime();
                callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                        now / TimeUtils.NANOS_PER_MS);
                if (callbacks == null) {
                    return;
                }
                mCallbacksRunning = true;
    
                if (callbackType == Choreographer.CALLBACK_COMMIT) {
                    final long jitterNanos = now - frameTimeNanos;
                    Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos);
                    if (jitterNanos >= 2 * mFrameIntervalNanos) {
                        final long lastFrameOffset = jitterNanos % mFrameIntervalNanos
                                + mFrameIntervalNanos;
                        frameTimeNanos = now - lastFrameOffset;
                        mLastFrameTimeNanos = frameTimeNanos;
                    }
                }
            }
            try {
                Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
                for (CallbackRecord c = callbacks; c != null; c = c.next) {
                    if (DEBUG_FRAMES) {
                        Log.d(TAG, "RunCallback: type=" + callbackType
                                + ", action=" + c.action + ", token=" + c.token
                                + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
                    }
                    c.run(frameTimeNanos);
                }
            } finally {
                synchronized (mLock) {
                    mCallbacksRunning = false;
                    do {
                        final CallbackRecord next = callbacks.next;
                        recycleCallbackLocked(callbacks);
                        callbacks = next;
                    } while (callbacks != null);
                }
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
        }
    

    doFrame处理的时间有五种,分别为Choreographer.CALLBACK_INPUT,Choreographer.CALLBACK_ANIMATION、Choreographer.CALLBACK_INSETS_ANIMATION、Choreographer.CALLBACK_TRAVERSAL、Choreographer.CALLBACK_COMMIT。在doFrame()会分别去执行对应的时间,调用其run方法。

    public void run(long frameTimeNanos) {
                if (token == FRAME_CALLBACK_TOKEN) {
                    ((FrameCallback)action).doFrame(frameTimeNanos);
                } else {
                    ((Runnable)action).run();
                }
            }
    

    这个token在前面提到过,从scheduleTraversals发送过来的Runnable会定义为Choreographer.CALLBACK_TRAVERSAL类型的时间,并且token为null.所以上面的代码会走else.而这个action就是传进来的mTraversalRunnable,会调用mTraversalRunnable的run方法。

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

    TraversalRunnable里面调用doTraversal(),doTraversal()里面就看到我们熟悉的view的绘制流程方法。因此,这个时候Handler正在执行着TraversalRunnable这个Runnable,而我们post的Runnable要等待TraversalRunnable执行完才会去执行,而TraversalRunnable这里面又会进行measure,layout和draw流程,所以等到执行我们的Runnable时,此时的View就已经被measure过了,所以获取到的宽高就是measure过后的宽高。

    相关文章

      网友评论

          本文标题:view.post()

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