zoukankan      html  css  js  c++  java
  • 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。

    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种类型事件进行回调。

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

    参考资料:

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

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

  • 相关阅读:
    题解 DTOJ #1438. 矮人排队(lineup)
    题解 DTOJ #4423. 「THUSC2019」塔
    题解 DTOJ #4123.「2019冬令营提高组」全连
    题解 DTOJ #4016.辉夜的夜空明珠(moon)
    题解 DTOJ #2498.大步小步(babystep)
    题解 DTOJ #3326.组队(group)
    题解 DTOJ #1515.三塔合一
    题解 DTOJ #2305.Bazarek
    【code】Splay 模板
    寻找乱序数组中第K大的数
  • 原文地址:https://www.cnblogs.com/renhui/p/12965850.html
Copyright © 2011-2022 走看看