yunshuipiao / Potato

Read the fucking source code for the Android interview
Apache License 2.0
80 stars 12 forks source link

Android Screen refresh mechanism #39

Open yunshuipiao opened 5 years ago

yunshuipiao commented 5 years ago

Android Screen refresh mechanism

[TOC]

这篇文章在 View 的绘制基础上,依旧从 ViewRootImpl 开始,探索与屏幕刷新之间的渊源。

View 的工作流程

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        // 同一帧不会多次调用遍历
        mTraversalScheduled = true;
        // handler 的同步屏障,发送一条异步消息
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();

        // 入口
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

上述有两个地方:

  1. postSyncBarrier: handler 中的同步屏障

    一般来说,同步屏障的作用时可以拦截 Looper 对同步消息的获取和分发。因为 handler 消息机制中 MessageQueue 会不断的取出 Message。加入同步屏障之后,Looper 只有获取和处理异步消息,如果没有异步消息,马么就会进入阻塞状态。

    原因子啊雨 View 的绘制和屏幕刷新时优先级最高的事情(防止卡顿),除了对 View 绘制的处理操作可以优先处理(异步消息),其他的 Message 都可以慢处理。

  2. Choreographer:编排。协调动画、输入和绘图的时间。从显示子系统接收定时脉冲(例如垂直同步),然后调度作为呈现下一个显示帧的一部分而发生的工作。

Choreographer

这个类包括三部分:

在 ViewRootImpl 的上述方法中,choreographer.postCallback() 方法执行,遍历绘制view。

// 执行要在下一帧上运行的回调
// 执行一次自动移除
@TestApi
public void postCallback(int callbackType, Runnable action, Object token) {
    postCallbackDelayed(callbackType, action, token, 0);
}
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;
      //根据时间将 action 添加到 mCallbackQueue 的队列中
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

        if (dueTime <= now) {
            scheduleFrameLocked(now);
        } else {
            Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
            msg.arg1 = callbackType;
            //设置异步延迟消息 ,过dueTime后执行(无视同步屏障)
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, dueTime);
        }
    }
}
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()) {
              // UI 线程
                scheduleVsyncLocked();
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                msg.setAsynchronous(true);
               //将异步消息放置Handler队列的最前面,当前是最高优先级。
                mHandler.sendMessageAtFrontOfQueue(msg);
            }
        }
    }
}

    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 {
            // 注册一个垂直同步脉冲VSYNC,当下一个脉冲到来时会回调dispatchVsync方法
            nativeScheduleVsync(mReceiverPtr);
        }
    }

VSYNC

VSYNC 的全称是 Vertical Synchronization ,即垂直同步。

由于人眼与大脑之间的协作一般情况无法感知超过60FPS的画面更新。如果所看到画面的帧率高于12帧的时候,就会认为是连贯的,达到24帧便是流畅的体验,这也就是胶片电影的播放速度(24FPS)

对于屏幕显示,游戏体验来说,如果能整体平稳的达到60FPS,画面每秒更新60次,也就是16.67ms刷新一次,绝大部分人视觉体验都会觉得非常流畅如丝般顺滑。

public float getRefreshRate() {
    synchronized (this) {
        updateDisplayInfoLocked();
        return mDisplayInfo.getMode().getRefreshRate();
    }
}

每秒钟 60 帧的屏幕刷新频率,也就是 1000 / 60 ≈ 16.67ms 。

在没有 VSYNC 同步信号脉冲情况下 : (Jank 为同一帧在屏幕上出现 2 次以上)

20131115192829593

20131123101929171

兜了一圈回到了ViewRootImpl,大致说明了我们当前的遍历操作,对下一帧的准备工作是,当我们ViewRootImpl遍历结束,将绘制结果交给屏幕以便显示。 如果迟迟交不出View的绘制结果,那么屏幕将会一直显示当前画面。

onVsync是底层回调回来的,那也就是每16.6ms,底层会发出一个屏幕刷新的信号,然后会回调到onVsync方法之中,但是有一点很奇怪,底层怎么知道上层是哪个app需要这个信号来刷新呢,结合日常开发,要实现这种一般使用观察者模式,将app本身注册下去,但是好像也没有看到哪里有往底层注册的方法,对了,再次回到上面的那个native方法,nativeScheduleVsync(mReceiverPtr),那么这个方法大体的作用也就是注册监听了,

同步屏障

当设置了同步屏障之后,next函数将会忽略所有的同步消息,返回异步消息。换句话说就是,设置了同步屏障之后,Handler只会处理异步消息。再换句话说,同步屏障为Handler消息机制增加了一种简单的优先级机制,异步消息的优先级要高于同步消息

总结