Android Framework 学习(八):屏幕刷新机制

一、什么是屏幕刷新机制

屏幕的刷新包括三个步骤:

  • CPU 计算屏幕数据
  • GPU 进一步处理和缓存
  • Display 将缓存中(buffer)的屏幕数据显示出来。
屏幕刷新机制包含以下几点要素,需要我们了解和掌握:
  • View 发起刷新的操作时,最终是走到了 ViewRootImpl 的 scheduleTraversals() 里去,然后这个方法会将遍历绘制 View 树的操作 performTraversals() 封装到 Runnable 里,传给 Choreographer,以当前的时间戳放进一个 mCallbackQueue 队列里,然后调用了 native 层的方法向底层注册监听下一个屏幕刷新信号事件。

  • 当下一个屏幕刷新信号发出的时候,如果对这个事件进行监听,那么底层会回调 onVsync() 方法来通知。当 onVsync() 被回调时,会发一个 Message 到主线程,将后续的工作切到主线程来执行。切到主线程的工作就是去 mCallbackQueue 队列里根据时间戳将之前放进去的 Runnable 取出来执行,而这些 Runnable 就是遍历绘制 View 树的操作 performTraversals()。遍历操作完成后,就会去绘制那些需要刷新的 View。

  • 当我们调用了 invalidate(),requestLayout(),等之类刷新界面的操作时,并不是马上就会执行这些刷新的操作,而是通过 ViewRootImpl 的 scheduleTraversals() 先向底层注册监听下一个屏幕刷新信号事件,然后等下一个屏幕刷新信号来的时候,才会去通过 performTraversals() 遍历绘制 View 树来执行这些刷新操作。

  • 导致界面刷新丢帧的原因有两类:一是遍历绘制 View 树计算屏幕数据的时间超过了 16.6ms;二是,主线程一直在处理其他耗时的消息,导致遍历绘制 View 树的工作迟迟不能开始,从而超过了 16.6 ms 底层切换下一帧画面的时机。第一个原因是因为我们写的布局有问题,需要进行优化了。而第二个原因则是我们常说的避免在主线程中做耗时的任务。针对第二个原因,系统已经引入了同步屏障消息的机制,尽可能的保证遍历绘制 View 树的工作能够及时进行,但仍没办法完全避免,所以我们还是得尽可能避免主线程耗时工作。

二、Choreographer机制

Choreographer机制,用于同Vsync机制配合,统一动画、输入和绘制的时机。

本节讲解Choreographer机制主要是从绘制方面做阐述,界面的绘制要从ViewRootImpl的requestLayout开始说起,其源码如下:

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();     // 检查是否在UI线程
            mLayoutRequested = true;  // 是否进行measure和layout布局
            scheduleTraversals();
        }
    }

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            // 拦截同步消息
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            // 执行绘制操作
       mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

这里需要注意的地方有两点:

  • postSyncBarrier()方法:Handler 的同步屏障,作用是可以拦截 Looper 对同步消息的获取和分发,加入同步屏障之后Looper 只会获取和处理异步消息,如果没有异步消息那么就会进入阻塞状态。通过同步屏障,就为UI绘制渲染处理操作的优先处理提供了基础。
  • Choreographer:编舞者,统一动画、输入和绘制时机。

1. Choreographer 启动逻辑

Choreographer的创建是在ViewRootImpl的构造函数中。

public ViewRootImpl(Context context, Display display) {
    mContext = context;
    mWindowSession = WindowManagerGlobal.getWindowSession();
    ...
    mChoreographer = Choreographer.getInstance();
    ...
}

下面是Choreographer的getInstance执行的代码:

    /**
     * Gets the choreographer for the calling thread.  Must be called from
     * a thread that already has a {@link android.os.Looper} associated with it.
     *
     * @return The choreographer for this thread.
     * @throws IllegalStateException if the thread does not have a looper.
     */
    public static Choreographer getInstance() {
        return sThreadInstance.get();
    }

    // Thread local storage for the choreographer.
    private static final ThreadLocal<Choreographer> sThreadInstance =
            new ThreadLocal<Choreographer>() {
        @Override
        protected Choreographer initialValue() {
            Looper looper = Looper.myLooper();
            if (looper == null) {
                throw new IllegalStateException("The current thread must have a looper!");
            }
            Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
            if (looper == Looper.getMainLooper()) {
                mMainInstance = choreographer;
            }
            return choreographer;
        }
    };

    private static volatile Choreographer mMainInstance;

可以看到每一个Looper线程都有自己的Choreographer,其他线程发送的回调只能运行在对应Choreographer所属的Looper线程上。

这里我们再看一下Choreographer的构造函数:

    private Choreographer(Looper looper, int vsyncSource) {
        mLooper = looper;
        mHandler = new FrameHandler(looper);
        mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper, vsyncSource) : null;
        mLastFrameTimeNanos = Long.MIN_VALUE;

        mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());

        mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
        for (int i = 0; i <= CALLBACK_LAST; i++) {
            mCallbackQueues[i] = new CallbackQueue();
        }
        // b/68769804: For low FPS experiments.
        setFPSDivisor(SystemProperties.getInt(ThreadedRenderer.DEBUG_FPS_DIVISOR, 1));
    }

Choreographer类中有一个Looper和一个FrameHandler变量。变量USE_VSYNC用于表示系统是否是用了Vsync同步机制,该值是通过读取系统属性debug.choreographer.vsync来获取的。如果系统使用了Vsync同步机制,则创建一个FrameDisplayEventReceiver对象用于请求并接收Vsync事件,最后Choreographer创建了一个大小为3的CallbackQueue队列数组,用于保存不同类型的Callback。

不同类型的Callback包括如下4种:

CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL、CALLBACK_COMMIT。

Android Framework 学习(八):屏幕刷新机制

CallbackQueue是一个容量为4的数组,每一个元素作为头指针,引出对应类型的链表,4种事件就是通过这4个链表来维护的。而FrameHandler中主要处理三类消息:

private final class FrameHandler extends Handler {
   public FrameHandler(Looper looper) {
       super(looper);
   }

   @Override
   public void handleMessage(Message msg) {
       switch (msg.what) {
           case MSG_DO_FRAME:
               doFrame(System.nanoTime(), 0);
               break;
           case MSG_DO_SCHEDULE_VSYNC:
               doScheduleVsync();   // 请求VSYNC信号
               break;
           case MSG_DO_SCHEDULE_CALLBACK:
               doScheduleCallback(msg.arg1);
               break;
       }
   }
}

2. Choreographer执行流程 

执行流程就要需要从下面的代码开始说起:

// 执行绘制操作
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);

最终会调到 postCallbackDelayedInternal 方法:

private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) {
   synchronized (mLock) {
       // 当前时间
       final long now = SystemClock.uptimeMillis();
       // 回调执行时间,为当前时间加上延迟的时间
       final long dueTime = now + delayMillis;
       // obtainCallbackLocked会将传入的3个参数转换为CallbackRecord,然后CallbackQueue根据回调类型将CallbackRecord添加到链表上。
       mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
       if (dueTime >= now) {
           // 如果delayMillis=0的话,dueTime=now,则会马上执行
           scheduleFrameLocked(now);
       } else {
           // 如果dueTime > now,则发送一个what为MSG_DO_SCHEDULE_CALLBACK类型的定时消息,等时间到了再处理,其最终处理也是执行scheduleFrameLocked(long now)方法
           Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
           msg.arg1 = callbackType;
           msg.setAsynchronous(true);
           mHandler.sendMessageAtTime(msg, dueTime);
       }
   }
}

mCallbackQueues先把对应的callback添加到链表上来,然后判断是否有延迟,如果没有则会马上执行scheduleFrameLocked,如果有,则发送一个what为MSG_DO_SCHEDULE_CALLBACK类型的定时消息,等时间到了再处理,其最终处理也是执行scheduleFrameLocked(long now)方法。

private void scheduleFrameLocked(long now) {
   if (!mFrameScheduled) {
       mFrameScheduled = true;
       if (USE_VSYNC) {
           // 如果使用了VSYNC,由系统值确定
           if (DEBUG_FRAMES) {
               Log.d(TAG, "Scheduling next frame on vsync.");
           }
           if (isRunningOnLooperThreadLocked()) {
               // 请求VSYNC信号,最终会调到Native层,Native处理完成后触发FrameDisplayEventReceiver的onVsync回调,回调中最后也会调用doFrame(long frameTimeNanos, int frame)方法
               scheduleVsyncLocked();
           } else {
               // 在UI线程上直接发送一个what=MSG_DO_SCHEDULE_VSYNC的消息,最终也会调到scheduleVsyncLocked()去请求VSYNC信号
               Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
               msg.setAsynchronous(true);
               mHandler.sendMessageAtFrontOfQueue(msg);
           }
       } else {
           // 没有使用VSYNC
           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.");
           }
           // 直接发送一个what=MSG_DO_FRAME的消息,消息处理时调用doFrame(long frameTimeNanos, int frame)方法
           Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
           msg.setAsynchronous(true);
           mHandler.sendMessageAtTime(msg, nextFrameTime);
       }
   }
}

判断USE_VSYNC,如果使用了VSYNC,走scheduleVsyncLocked,即请求VSYNC信号,最终调用doFrame;如果没使用VSYNC,则通过异步Message执行doFrame。

下面我们看一下doFrame的代码:

void doFrame(long frameTimeNanos, int frame) {
    final long startNanos;
    synchronized (mLock) {
        if (!mFrameScheduled) {
            return; // mFrameScheduled=false,则直接返回。
        }
        long intendedFrameTimeNanos = frameTimeNanos; //原本计划的绘帧时间点
        startNanos = System.nanoTime();//保存起始时间
        //由于Vsync事件处理采用的是异步方式,因此这里计算消息发送与函数调用开始之间所花费的时间
        final long jitterNanos = startNanos - frameTimeNanos;
        //如果线程处理该消息的时间超过了屏幕刷新周期
        if (jitterNanos >= mFrameIntervalNanos) {
            //计算函数调用期间所错过的帧数
            final long skippedFrames = jitterNanos / mFrameIntervalNanos;
            //当掉帧个数超过30,则输出相应log
            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;
            frameTimeNanos = startNanos - lastFrameOffset; //对齐帧的时间间隔
        }
       //如果frameTimeNanos小于一个屏幕刷新周期,则重新请求VSync信号
        if (frameTimeNanos < mLastFrameTimeNanos) {
            scheduleVsyncLocked();
            return;
        }
        mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
        mFrameScheduled = false;
        mLastFrameTimeNanos = frameTimeNanos;
    }
    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
        //分别回调CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL事件
        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 {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

doCallBacks里的run方法执行了,也就是真正执行了View的绘制流程了。

3.Choreographer总结

1). Choreographer支持4种类型事件:输入、绘制、动画、提交,并通过postCallback在对应需要同步vsync进行刷新处进行注册,等待回调。

2). Choreographer监听底层Vsync信号,一旦接收到回调信号,则通过doFrame统一对java层4种类型事件进行回调。

三、屏幕绘制过程的流程图

Android Framework 学习(八):屏幕刷新机制

 

参考资料:

https://www.jianshu.com/p/0d00cb85fdf3

https://www.jianshu.com/p/bab0b454e39e

Android Framework 学习(八):屏幕刷新机制

上一篇:C#委托基础4——泛型委托Func


下一篇:设计多选按钮ListChooseView