Android屏幕的刷新機制

最近的冠狀病毒搞得人心惶惶,待在家裏面也沒啥事,正好趁這段時間學習一下。瞭解一下Android屏幕的刷新機制對於我們解決應用卡頓也有幫助。

1.屏幕刷新機制

1)屏幕顯示

首先我們看下Android屏幕的顯示原理,如下圖所示
在這裏插入圖片描述
應用需要界面重繪時,會向系統申請buffer,拿到buffer之後會將圖像信息寫入buffer當中提交給系統,然後屏幕從緩衝區拿到圖像數據進行顯示。
Android手機一般都是60幀/秒,也就是每16ms屏幕從緩衝區讀取圖像數據進行刷新,如果沒有新的圖像數據更新,則會一直讀到舊的圖像數據,顯示舊的圖像。

2)VSync信號

屏幕都是週期性的刷新,每個週期,系統均會產生一個VSync信號,這樣就會產生一個問題,如下圖所示
在這裏插入圖片描述
應用繪製完第一幀,恰好在VSync週期內,然後在下一個週期,屏幕就能夠顯示第一幀;
但是第二幀繪製的時間處於兩個VSync週期之間,所以當第二幀繪製完成之後,只有在第四個週期才能夠顯示,然後導致第三幀只能在第五個週期顯示,然而第三幀在第三個週期內就已經繪製完成。這樣就會導致應用的卡頓。
所以爲了解決這個問題,在VSync信號來臨的時候才進行繪製,如下圖所示,讓應用的繪製和屏幕的顯示保持同步。
而實現保持同步功能的類就是Choreographer。
在這裏插入圖片描述

2.Choreographer

首先我們來看下ViewRootImpl.requestLayout,應用端請求刷新界面。
我們可以看到在註釋2處,增加了一個消息屏障,在屏障之後的所有普通消息都暫時不能夠執行,只有撤除屏障之後才能夠執行,但是異步消息不受屏障的影響,這樣做的好處就是讓真正重要的消息能夠優先執行。
另外我們需要注意的是,在註釋1處,判斷了mTraversalScheduled爲false,纔會post異步消息,而一旦執行post,mTraversalScheduled就被置爲true,而只有當下一個VSync信號來臨之時,執行了mTraversalRunnable,mTraversalScheduled才能重新被設置爲false。所以如果應用端同時發起多次requestLayout,在一個VSync週期內,也只能執行一次繪製。

public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

void scheduleTraversals() {
    //1.mTraversalScheduled爲false才執行
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        //2.增加一個消息屏障
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        //3.post了一個異步消息
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

接着我們來看下postCallback,最終會執行到postCallbackDelayedInternal,先看註釋1,mCallbackQueues是一個單鏈表數組,根據callbacyType,將action插入到鏈表當中。
我們傳入的delayMills爲0,所以會執行到註釋2處。
註釋3處,如果在當前線程,則直接執行scheduleVsyncLocked,否則使用handler post到指定的線程執行,scheduleVsyncLocked的作用就是告訴SurfaceFlinger,當下一個VSync信號來臨的時候,我們能夠收到回調。

private void postCallbackDelayedInternal(int callbackType,
        Object action, Object token, long delayMillis) {
   ......
    synchronized (mLock) {
        final long now = SystemClock.uptimeMillis();
        final long dueTime = now + delayMillis;
        //1.單鏈表,根據callbackType將action插入到鏈表當中
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
        if (dueTime <= now) {
            //2.執行scheduleFrameLocked
            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) {
    ......
           //3.如果在當前線程,則直接執行scheduleVsyncLocked,否則發送msg到對應的線程執行scheduleVsyncLocked
            if (isRunningOnLooperThreadLocked()) {
                scheduleVsyncLocked();
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtFrontOfQueue(msg);
            }
......
}

private void scheduleVsyncLocked() {
    //4.當VSync信號來臨時,mDisplayEventReceiver能夠收到回調
    mDisplayEventReceiver.scheduleVsync();
}

然後我們來看下回調的地方,當VSync信號來臨的時候,會回調到onVsync函數裏面,然後封裝一個msg,使用handler發出,最終會執行run方法裏面的doFrame。

private final class FrameDisplayEventReceiver extends DisplayEventReceiver
        implements Runnable {
    private boolean mHavePendingVsync;
    private long mTimestampNanos;
    private int mFrame;

    public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
        super(looper, vsyncSource);
    }

    @Override
    public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
        ......
        mTimestampNanos = timestampNanos;
        mFrame = frame;
        //1.將this傳入,即Runnable,msg爲一個異步消息
        Message msg = Message.obtain(mHandler, this);
        msg.setAsynchronous(true);
        mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
    }

    @Override
    public void run() {
        mHavePendingVsync = false;
        doFrame(mTimestampNanos, mFrame);
    }
}

再來看一下doFrame都幹了些啥
首先看下在註釋1處,用當前時間減去幀的時間得到延時時間,並用延時時間除以幀的週期,如果超過了規定的常量,就會打印“The application may be doing too much work on its main thread.”,表明應用在主線程做了太多的工作,導致跳了太多幀,需要優化。
註釋2處顯示了處理各種類型的callback,其中frameTimeNanos是幀的時間戳,只有到了時間的callback才能夠被處理。

void doFrame(long frameTimeNanos, int frame) {
       ......
        long intendedFrameTimeNanos = frameTimeNanos;
        startNanos = System.nanoTime();
        //1.當前時間-幀時間表示延時的時間
        final long jitterNanos = startNanos - frameTimeNanos;
        if (jitterNanos >= mFrameIntervalNanos) {
            final long skippedFrames = jitterNanos / mFrameIntervalNanos;
            if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
                Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
                        + "The application may be doing too much work on its main thread.");
            }

      ......
    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
        AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
       //2.處理callback
        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 {
        AnimationUtils.unlockAnimationClock();
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }

    ......
}

然後我們再來看下之前傳入的callback是啥,如下所示是CALLBACK_TRAVERSAL類型的,
而mTranversalRunnable是一個Runnable,最終會執行doTraversal函數,其中performTraversals就是真正開始界面的繪製。

void scheduleTraversals() {
    //1.mTraversalScheduled爲false才執行
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        //2.增加一個消息屏障
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        //3.post了一個異步消息
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}


void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }
       //4.真正的繪製界面
        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

3.總結

繞來繞去是不是都有點兒暈了,其實弄懂之後流程也不是特別複雜,以下是流程圖可以幫助理解
在這裏插入圖片描述

發佈了131 篇原創文章 · 獲贊 21 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章