• vie的刷新机制


    前言

    平时只知道在调用invalidate, requestLayout,或者 通过动画能够刷新屏幕,实现想要的ui效果,但是对view的刷新机制并不了解,本文记录了我对以下几个问题的思考和总结。

    • view多长时间刷新一次
    • view什么时机刷新
    • view如何刷新
    • 如果界面不变化,还需要刷新吗
    • 只要调用invalidate, requestLayout等函数就会会被立即刷新吗

    view多长时间刷新一次

    Android系统每隔16ms会发出VSYNC信号重绘我们的界面(Activity).
    为什么是16ms, 因为Android设定的刷新率是60FPS(Frame Per Second), 也就是每秒60帧的刷新率, 约合16ms刷新一次.

    对于一个系统来说,可以分为CPU,GPU和显示器三个部分,CPU负责计算,GPU对计算的数据进行渲染,然后放到缓冲区中存起来,显示器每隔一个固定的频率去取渲染好的数据显示出来。显示器刷新频率是固定的,但是CPU和GPU的计算和渲染时间却是没有规律的,假设GPU的渲染速率是瞬间完成的,那主要的时间因素就取决于CPU,CPU计算的过程其实就是View树的绘制过程,即从根布局开始,遍历所有的view分别执行测量、布局、绘制的过程,如果我么的界面过于复杂,在16ms内没有计算完成,那显示器取到的就是旧的数据,这就是掉帧,对用户来说就会觉得卡顿。

    在这里插入图片描述

    上图就是发生了一次典型的掉帧过程。

    view刷新时机

    我们都知道invalidate(), requestLayout()函数会让view进行重绘,但是一旦调用就立即开始重新绘制了吗?上面提到的VSYNC信号有什么作用?

    我们从View的invalidate函数开始看看整个执行流程

    public void invalidate(Rect dirty) {
            final int scrollX = mScrollX;
            final int scrollY = mScrollY;
            invalidateInternal(dirty.left - scrollX, dirty.top - scrollY,
            dirty.right - scrollX, dirty.bottom - scrollY, true, false);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    invalidateInternal()函数中会递归的调用parent的invaldateChild()函数,最终会调用到ViewRootImpl的invalidate()方法

    void invalidate() { 
        mDirty.set(0, 0, mWidth, mHeight); 
        if (!mWillDrawSoon) { 
            scheduleTraversals(); 
        } 
    } 
    
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            // 插入一个同步消息屏障
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    mTraversalRunnable是一个Runnable对象,它内部执行了doTraversal()方法,这个方法中才真正的开始遍历和绘制操作,从上面代码中可以知道,它并不是立即开始绘制,而是通过mChoreographer对象注册了一个回调,在这个postCallback中会请求同步VSYNC信息。

    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);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    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);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    因为加了同步消息屏障,所以这里创建了一个异步的MSG_DO_FRAME消息,这里要看一个很重要的类FrameDisplayEventReceiver

        private final class FrameDisplayEventReceiver extends DisplayEventReceiver
                implements Runnable {
            private boolean mHavePendingVsync;
            private long mTimestampNanos;
            private int mFrame;
            private VsyncEventData mLastVsyncEventData = new VsyncEventData();
    
            public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
                super(looper, vsyncSource, 0);
            }
    
            // TODO(b/116025192): physicalDisplayId is ignored because SF only emits VSYNC events for
            // the internal display and DisplayEventReceiver#scheduleVsync only allows requesting VSYNC
            // for the internal display implicitly.
            @Override
            public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
                    VsyncEventData vsyncEventData) {
                try {
                    if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
                        Trace.traceBegin(Trace.TRACE_TAG_VIEW,
                                "Choreographer#onVsync " + vsyncEventData.id);
                    }
                    // 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;
                    mLastVsyncEventData = vsyncEventData;
                    Message msg = Message.obtain(mHandler, this);
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
                } finally {
                    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
                }
            }
    
            @Override
            public void run() {
                mHavePendingVsync = false;
                doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    这个类会接收到底层的VSYNC信号,VSync信号由SurfaceFlinger实现并定时发送,FrameDisplayEventReceiver收到信号后,调用onVsync方法组织消息发送到主线程处理。这个消息主要内容就是run方法里面的doFrame()方法。也就是说,我们必须先提前注册,底层的VSYNC信号产生时才能回调到我们的app中,否则接受不到这个信号。

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

    总结

    综上,可以总结出

    • 只有同步信号VSYNC到来界面才会刷新
    • UI如果没有变化,则不会请求同步信号,界面不会刷新
    • 同步信号VSYNC需要申请才会有。
    • 同一帧内,如果有多个重绘的请求,scheduleTraversals() 会将其过滤掉,只需要安排一次绘制任务就行了,在下一次VSYNC信号到来时才会调用**performTraversals()**遍历view树并重绘。
    • 为了保证绘制任务优先被执行,ViewRootImpl会插入一个同步消息屏障,同步消息不会被处理,以此尽可能的保证在接收到VSYNC信号后能够第一时间进行处理。
  • 相关阅读:
    第四百四十四回
    【docker快速部署微服务若依管理系统(RuoYi-Cloud)】
    MySQL8.0优化 - 锁 - 从数据操作的类型划分:读锁、写锁
    通过 MSE 实现基于Apache APISIX的全链路灰度
    Linux利用源码包升级Nginx到1.23.1用以解决nginx安全漏洞问题
    自动配置和 thymeleaf模板引擎
    Rougamo、Fody 实现静态Aop
    vue3——使用axios
    如何利用Python中实现高效的网络爬虫
    论文笔记:多标签学习——ACkEL算法
  • 原文地址:https://blog.csdn.net/qq_41818873/article/details/125419315