Android进阶 -- 屏幕刷新机制

关于屏幕卡顿,从表象来讲,是因为主线程有耗时操作,导致屏幕绘制掉帧,屏幕每16毫秒会刷新一次,也就是每秒刷新60次,人眼能感觉到的卡顿的帧率是每秒24帧。所以解决卡顿的一般处理方法就是将耗时操作放在子线程、减少View层级、多使用include merge viewstub标签等,来保障屏幕绘制的流畅。

不过再往深里看,会有这样一些问题:卡顿的底层原因是什么?如何理解16毫米刷新一次?假如界面没有更新操作,View会每16毫秒刷新一次吗?

带着这些问题,来看下面的文字。

1.View

View里的requestLayout方法,常用来主动请求UI的更新,我们从这个方法看起

public void requestLayout() {
    …
    if (mParent != null && !mParent.isLayoutRequested()) {
        mParent.requestLayout();
    }
    …
}

View的requestLayout方法实际上调用的是mParent的requestLayout方法,而mParent是ViewParent的实例,再来看ViewParent接口

/**
 * Defines the responsibilities for a class that will be a parent of a View.
 * This is the API that a view sees when it wants to interact with its parent.
 * 
 */
public interface ViewParent {
    /**
     * Called when something has changed which has invalidated the layout of a
     * child of this view parent. This will schedule a layout pass of the view
     * tree.
     */
    public void requestLayout();
}

这里的mParent.requestLayout最终会调用到ViewRootImpl的requestLayout方法,因为根View是DecorView,而DecorView的parent就是ViewRootImpl。

 

2.ViewRootImpl

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

ViewRootImpl里的requestLayout方法只有4行代码,首先调用了 checkThread()方法,用来做线程的检查,点进去就看到了大名鼎鼎的异常信息

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

在看scheduleTraversals()方法

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

mTraversalScheduled标志位,这个标志位为false时才可以刷新UI,设置这个标志位的原因是防止短时间多次调用requestLayout重复绘制。

mHandler.getLooper().getQueue().postSyncBarrier()这里发送了一条同步屏障消息,这时候消息队列中的同步消息不会被处理,而是优先处理异步消息。

mChoreographer.postCallback向Choreographer里提交了一个mTraversalRunnable任务,这个任务不会马上执行。

 

3.Choreographer

postCallback实际调用的方法是postCallbackDelayedInternal

private void postCallbackDelayedInternal(int callbackType,
        Object action, Object token, long 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);
        }
    }
}

addCallbackLocked 将任务添加到队列,不会马上执行。

scheduleFrameLocked 通常delayMillis是0 执行scheduleFrameLocked方法

private void scheduleFrameLocked(long now) {
    if (!mFrameScheduled) {
        mFrameScheduled = true;
        if (USE_VSYNC) {
            if (DEBUG_FRAMES) {
                Log.d(TAG, "Scheduling next frame on vsync.");
            }

            // If running on the Looper thread, then schedule the vsync immediately,
            // otherwise post a message to schedule the vsync from the UI thread
            // as soon as possible.
            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);
            if (DEBUG_FRAMES) {
                Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
            }
            Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, nextFrameTime);
        }
    }
}

scheduleFrameLocked方法判断当前线程是否为UI线程,如果是UI线程则执行scheduleVsyncLocked方法,如果非UI线程就通过Handler处理,再来看scheduleVsyncLocked方法

private void scheduleVsyncLocked() {
    mDisplayEventReceiver.scheduleVsync();
}

在来看DisplayEventReceiver

/**
 * Schedules a single vertical sync pulse to be delivered when the next
 * display frame begins.
 */
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);
    }
}

// Called from native code.
@SuppressWarnings("unused")
private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
    onVsync(timestampNanos, builtInDisplayId, frame);
}

scheduleVsync方法通过底层代码请求vsync,vsync来的时候底层jni代码会调用dispatchVsync
方法,然后回调onVsync这个空方法,由使用的类去实现

public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
}

这里是屏幕刷新机制的重点,应用必须向底层请求vsync信号,然后下一次vsync信号来的时候会通过jni通知到应用,然后才是应用的绘制逻辑。

Choreographer的内部类FrameDisplayEventReceiver继承了DisplayEventReceiver,并重写了onVsync方法

@Override
    public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
        // Ignore vsync from secondary display.
        // This can be problematic because the call to scheduleVsync() is a one-shot.
        // We need to ensure that we will still receive the vsync from the primary
        // display which is the one we really care about.  Ideally we should schedule
        // vsync for a particular display.
        // At this time Surface Flinger won't send us vsyncs for secondary displays
        // but that could change in the future so let's log a message to help us remember
        // that we need to fix this.
        if (builtInDisplayId != SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
            Log.d(TAG, "Received vsync from secondary display, but we don't support "
                    + "this case yet.  Choreographer needs a way to explicitly request "
                    + "vsync for a specific display to ensure it doesn't lose track "
                    + "of its scheduled vsync.");
            scheduleVsync();
            return;
        }

        // Post the vsync event to the Handler.
        // The idea is to prevent incoming vsync events from completely starving
        // the message queue.  If there are no messages in the queue with timestamps
        // earlier than the frame time, then the vsync event will be processed immediately.
        // Otherwise, messages that predate the vsync event will be handled first.
        long now = System.nanoTime();
        if (timestampNanos > now) {
            Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
                    + " ms in the future!  Check that graphics HAL is generating vsync "
                    + "timestamps using the correct timebase.");
            timestampNanos = now;
        }

        if (mHavePendingVsync) {
            Log.w(TAG, "Already have a pending vsync event.  There should only be "
                    + "one at a time.");
        } 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);
    }
}

当收到vsync信号后,onVsync方法就会被调用,通过Handler往消息队列里插入了一个异步消息并指定执行时间,并在callback里传入了this(Message.obtain(mHandler, this),所以最后会回调run方法,run方法里调用了doFrame方法,如果Handler此时存在耗时操作,那么需要等耗时操作执行完成,Looper才会轮询到下一条消息,run方法才会调用,在来看doFrame方法

void doFrame(long frameTimeNanos, int frame) {
    final long startNanos;
    synchronized (mLock) {
        if (!mFrameScheduled) {
            return; // no work to do
        }

        if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) {
            mDebugPrintNextFrameTimeDelta = false;
            Log.d(TAG, "Frame time delta: "
                    + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
        }

        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) {
                Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
                        + "The application may be doing too much work on its main thread.");
            }
            final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
            if (DEBUG_JANK) {
                Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
                        + "which is more than the frame interval of "
                        + (mFrameIntervalNanos * 0.000001f) + " ms!  "
                        + "Skipping " + skippedFrames + " frames and setting frame "
                        + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
            }
            frameTimeNanos = startNanos - lastFrameOffset;
        }

        if (frameTimeNanos < mLastFrameTimeNanos) {
            if (DEBUG_JANK) {
                Log.d(TAG, "Frame time appears to be going backwards.  May be due to a "
                        + "previously skipped frame.  Waiting for next vsync.");
            }
            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);

        mFrameInfo.markPerformTraversalsStart();
        doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);

        doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
    } finally {
        AnimationUtils.unlockAnimationClock();
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }

    if (DEBUG_FRAMES) {
        final long endNanos = System.nanoTime();
        Log.d(TAG, "Frame " + frame + ": Finished, took "
                + (endNanos - startNanos) * 0.000001f + " ms, latency "
                + (startNanos - frameTimeNanos) * 0.000001f + " ms.");
    }
}

doFrame方法较长,一点一点来看
final long jitterNanos = startNanos - frameTimeNanos;当前时间减去vsync来的时间,也就是主线程的耗时时间

final long skippedFrames = jitterNanos / mFrameIntervalNanos;1帧是16毫秒,计算当前跳过了多少帧,比如超时163毫秒,那么就是跳过了10帧

if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) { Log.i(TAG, "Skipped " + skippedFrames + " frames! " + "The application may be doing too much work on its main thread."); }
这里又看到了一个大名鼎鼎的log信息,SKIPPED_FRAME_WARNING_LIMIT默认是30,如果超过了30,就log提示

if (frameTimeNanos < mLastFrameTimeNanos) 如果时间倒退了,就等下一个vsync信号,一般不会走到这里

正常绘制会走到洗面的doCallbacks里,在看doCallbacks里的逻辑

void doCallbacks(int callbackType, long frameTimeNanos) {
    CallbackRecord callbacks;
    synchronized (mLock) {
        // We use "now" to determine when callbacks become due because it's possible
        // for earlier processing phases in a frame to post callbacks that should run
        // in a following phase, such as an input event that causes an animation to start.
        final long now = System.nanoTime();
        callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                now / TimeUtils.NANOS_PER_MS);
        if (callbacks == null) {
            return;
        }
        mCallbacksRunning = true;

        // Update the frame time if necessary when committing the frame.
        // We only update the frame time if we are more than 2 frames late reaching
        // the commit phase.  This ensures that the frame time which is observed by the
        // callbacks will always increase from one frame to the next and never repeat.
        // We never want the next frame's starting frame time to end up being less than
        // or equal to the previous frame's commit frame time.  Keep in mind that the
        // next frame has most likely already been scheduled by now so we play it
        // safe by ensuring the commit time is always at least one frame behind.
        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;
                if (DEBUG_JANK) {
                    Log.d(TAG, "Commit callback delayed by " + (jitterNanos * 0.000001f)
                            + " ms which is more than twice the frame interval of "
                            + (mFrameIntervalNanos * 0.000001f) + " ms!  "
                            + "Setting frame time to " + (lastFrameOffset * 0.000001f)
                            + " ms in the past.");
                    mDebugPrintNextFrameTimeDelta = true;
                }
                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);
    }
}

callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked先取出队列里的任务(在postCallbackDelayedInternal方法里添加进来的)

if (callbackType == Choreographer.CALLBACK_COMMIT)是流程的最后一步,数据已经绘制完准备提交的时候,会更正一下时间戳,确保提交时间总是在最后一次vsync时间之后

 c.run(frameTimeNanos) 还没到最后一步的时候,取出其它任务出来run,这个任务肯定就是跟View的绘制相关了,记得开始requestLayout传过来的类型吗,Choreographer.CALLBACK_TRAVERSAL,从队列get出来的任务类对应是mTraversalRunnable,类型是TraversalRunnable,定义在ViewRootImpl里面,饶了一圈,回到ViewRootImpl继续看

void scheduleTraversals() { 
if (!mTraversalScheduled) { 
... 
mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); } }

这个mTraversalRunnable 任务绕了一圈,通过请求vsync信号,到收到信号,然后终于被调用了。

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

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

        performTraversals();

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

先移除同步屏障消息,然后调用performTraversals 方法,这个方法大家都比较熟悉了,View的三个方法回调measure、layout、draw都在里面,需要注意的点是LinearLayout设置权重的情况下会measure两次。

 

小结:

View的requestLayout方法会调用到ViewRootImpl的requestLayout方法,然后通过scheduleTraversals方法向Chaoreographer提交一个绘制任务,然后在通过DisplayEventReceiver方法向Choreographer提交一个绘制任务,然后再通过DisplayEventReceiver向底层请求vsync信号,当vsync信号来的时候,会通过jni回调回来,通过Handler往主线程消息队列post一个异步任务,最终是viewRootImpl去执行绘制任务,调用performTraversals方法,里面是View的三个方法回调。

现在,相信前面提到问题已经有答案了

应用需要主动请求vsync,vsync来的时候才会通过jni通知到应用,然后才调用View的三个绘制方法,如果没有发起绘制请求,例如没有requestLayout,View的绘制方法是不会被调用的。ViewRootimpl里面的这个View其实是DecorView

有两个地方会造成掉帧
一个是主线程有其他耗时操作,导致doFrame没有机会再vsync信号发出之后16毫米内调用,对应下图的3
另一个是当前doFrame方法耗时,绘制太久,下一个vsync信号来的时候,这一帧还没有画完,对应2
1是正常情况


Android进阶 -- 屏幕刷新机制_第1张图片

你可能感兴趣的:(你好,Android)