view.post()

2021-03-02  本文已影响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过后的宽高。

上一篇下一篇

猜你喜欢

热点阅读