当前位置:网站首页>Refresh mechanism of vie

Refresh mechanism of vie

2022-06-25 02:40:00 Dongdongxu Huster

Preface

Usually, I only know that I am calling invalidate, requestLayout, perhaps The screen can be refreshed through animation , Achieve what you want ui effect , But yes. view I don't understand the refresh mechanism of , This paper records my thinking and summary on the following issues .

  • view How often do I refresh
  • view When to refresh
  • view How to refresh
  • If the interface doesn't change , Do you still need to refresh
  • Just call invalidate, requestLayout Wait for the function to be refreshed immediately

view How often do I refresh

Android The system every 16ms Will be issued VSYNC Signal redraw our interface (Activity).
Why 16ms, because Android The set refresh rate is 60FPS(Frame Per Second), That is, every second 60 Refresh rate of frame , About us 16ms Refresh once .

For a system , Can be divided into CPU,GPU And the display ,CPU Responsible for calculation ,GPU Render the calculated data , Then put it in the buffer and save it , The display takes the rendered data at a fixed frequency and displays it . The display refresh rate is fixed , however CPU and GPU The calculation and rendering time is irregular , hypothesis GPU The rendering rate is instantaneous , The main time factor depends on CPU,CPU The process of calculation is actually View Tree drawing process , That is, start from the root layout , Traverse all view Perform measurements separately 、 Layout 、 The process of drawing , If our interface is too complex , stay 16ms The calculation is not completed within , What the monitor gets is old data , This is the frame drop , For users, they will feel stuck .

 Insert picture description here

The figure above shows a typical frame dropping process .

view Refresh time

We all know invalidate(), requestLayout() The function makes view Redraw , But is redrawing started as soon as it is called ? As mentioned above VSYNC What does the signal do ?

We from View Of invalidate Function to see the whole execution process

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);
}

invalidateInternal() Function will be called recursively parent Of invaldateChild() function , Will eventually be called to ViewRootImpl Of invalidate() Method

void invalidate() {
     
    mDirty.set(0, 0, mWidth, mHeight); 
    if (!mWillDrawSoon) {
     
        scheduleTraversals(); 
    } 
} 

void scheduleTraversals() {
    
    if (!mTraversalScheduled) {
    
        mTraversalScheduled = true;
        //  Insert a synchronous message barrier 
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
            Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

mTraversalRunnable It's a Runnable object , It internally executes doTraversal() Method , In this method, the traversal and drawing operations are really started , As you can see from the above code , It doesn't start drawing immediately , But through mChoreographer Object registers a callback , In this postCallback Synchronization will be requested in VSYNC Information .

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) {
            //  Apply for synchronization signal 
            scheduleFrameLocked(now);
        } else {
            Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
            msg.arg1 = callbackType;
            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()) {
    
                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);
        }
    }
}

Because the synchronization message barrier is added , So here we create an asynchronous MSG_DO_FRAME news , Here is a very important class 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);
        }
    }

This class will receive the underlying VSYNC The signal ,VSync The signal is from SurfaceFlinger Realize and send regularly ,FrameDisplayEventReceiver After receiving the signal , call onVsync Method organizes messages sent to the main thread for processing . The main content of this message is run Method doFrame() Method . in other words , We must register in advance , At the bottom VSYNC Only when the signal is generated can it be recalled to our app in , Otherwise, the signal will not be received .

So , When we call invalidate(),requestLayout(), And so on , These refresh operations will not be performed immediately , But through ViewRootImpl Of scheduleTraversals() First register with the bottom layer to listen for the next screen refresh signal event , Then when the next screen refresh signal comes , Will go through performTraversals() Ergodic rendering View Tree to perform these refresh operations .

summary

Sum up , It can be concluded that

  • Only synchronization signals VSYNC The interface will be refreshed when it arrives
  • UI If there is no change , The synchronization signal is not requested , The interface doesn't refresh
  • Synchronous signal VSYNC You need to apply to have .
  • In the same frame , If there are multiple redrawing requests ,scheduleTraversals() Will filter it out , You only need to schedule a drawing task , The next time VSYNC Only when the signal arrives will it call **performTraversals()** Traverse view Tree union redrawing .
  • In order to ensure that the drawing task is performed first ,ViewRootImpl A synchronization message barrier will be inserted , Synchronization messages will not be processed , So as to ensure as much as possible that VSYNC The signal can be processed at the first time .
原网站

版权声明
本文为[Dongdongxu Huster]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/176/202206242312305056.html