卡頓優(yōu)化①Android UI渲染和刷新機制

丟幀和卡頓

卡頓,是字面意思上來講间景,就是畫面不流暢佃声,即頁面刷新不連貫。Android系統(tǒng)默認的頁面刷新頻率是60幀倘要,每秒刷新60次圾亏,即屏幕上的畫面16.6ms刷新一次,這個頻率是由手機設備的屏幕硬件來控制的。如果16.6ms沒有完成一次刷新碗誉,就造成了丟幀召嘶。大部分的App偶爾間歇性地出現(xiàn)幾次丟幀,不會造成明顯的卡頓哮缺,只有連續(xù)或者短時間多次出現(xiàn)丟幀弄跌,就會讓用戶感覺到明顯的卡頓現(xiàn)象。

UI渲染機制

手機屏幕由一個個的像素點組成尝苇,如1920x1080分辨率的屏幕就表示在屏幕上橫向每行有1080個像素點铛只,縱向每列有1920個像素點。也可以想象成二維數(shù)組糠溜。數(shù)組中的每個數(shù)值淳玩,代表對應的像素點顯示的顏色。屏幕畫面每隔16.6ms刷新一次非竿,也就是更新每個像素點要顯示的顏色蜕着。而這個16.6ms的刷新頻率,也就是60FPS,是由手機屏幕硬件來控制的承匣。

系統(tǒng)是依據(jù)什么來確定每個像素點應該顯示的顏色呢蓖乘?由此引出了Frame Buffer的概念。Frame Buffer是系統(tǒng)的幀緩沖區(qū)韧骗,可以理解為屏幕顯示的抽象嘉抒。
image.png
結(jié)合上面的圖示,每一個Surface都對應一個Buffer Queue袍暴,由SufaceFlinger管理些侍。Buffer Queue內(nèi)部包含兩個Graphic Buffer。頁面刷新時政模,繪制內(nèi)容最終經(jīng)過柵格化后存放在Graphic Buffer中岗宣。其中Front Buffer里存放的是頁面當前正在顯示這一幀的內(nèi)容。頁面需要更新時览徒,將先下一幀要顯示的內(nèi)容存放在Offscreen Buffer狈定,再通過Swap Buffer復制到Front Buffer中颂龙。

同時可能會有多個Surface习蓬,它們可能來自不同的應用,也可能是同一個應用里面類似SurfaceView和TextureView措嵌,它們也都有自己單獨的Surface躲叼。屏幕刷新時,SufaceFlinger從每一個Surface的Front Buffer中企巢,拿到要顯示的內(nèi)容枫慷。然后將所有Surface要顯示的內(nèi)容,統(tǒng)一交給Hardware Composer浪规,它會根據(jù)位置或听,Z-Order順序等信息合成最終要顯示顯示在屏幕上的內(nèi)容,而這個內(nèi)容就會交給系統(tǒng)的幀緩沖區(qū)Frame Buffer來顯示笋婿。也就是說誉裆,系統(tǒng)根據(jù)Frame Buffer中的內(nèi)容來確定每一個像素點在每一幀要顯示的顏色。
至此缸濒,我們了解了Frame Buffer的作用足丢,并由此引出了Surface,SurfaceFlinger庇配,Graphic Buffer等圖形組件的概念斩跌。這里引用一篇文章中的比喻來讓大家對整個圖形繪制系統(tǒng)的整體架構有個大概的了解,然后再進一步深入了解捞慌。
如果把應用程序的頁面渲染過程當做是一次繪畫過程耀鸦,那么繪畫過程中Android的各個圖形組件的作用是:

  • 畫筆:Skia或者OpenGL。我們可以用Skia畫筆來繪制2D圖形啸澡,也可以用OpenGL畫筆來繪制2D/3D圖形袖订。前者使用CPU繪制萝快,或者使用GPU繪制。
  • 畫布:Surface著角。所有的內(nèi)容元素都在Surface這張畫紙上進行繪制和渲染揪漩。在Android中首昔,Window是View的容器哮内,每一個Window都會關聯(lián)一個Surface。而WindowManager負責管理這些Window啡邑,并把他們的數(shù)據(jù)傳遞給SurfaceFlinger产徊。
  • 畫板:Graphic Buffer昂勒。Graphic Buffer緩沖用于應用程序的頁面繪制,在Android4.1之前使用的是雙緩沖機制舟铜。Android4.1之后戈盈,使用的是三緩沖機制。
    顯示:SurfaceFlinger谆刨。它將WindowManager提供的所有Surface塘娶,通過硬件合成器Hardware Composer合成并輸出到顯示屏。

CPU和GPU

整個UI渲染機制主要依賴三個硬件:CPU痊夭、GPU和屏幕刁岸。上面已經(jīng)介紹過了。下圖則展示了在UI渲染過程中她我,CPU和GPU分別負責的工作虹曙。
image.png

UI組件繪制到屏幕之前,都需要經(jīng)過柵格化(Rasterization)操作番舆,而柵格化操作又是一個相對耗時的操作酝碳。相比于CPU,GPU更擅長處理圖形運算恨狈,可以加快柵格化過程疏哗。這也是通常所說的硬件加速繪制的原理,將CPU不擅長的圖形計算轉(zhuǎn)換為GPU的專有指令來處理拴事。

Android3.0之前沃斤,或者沒有啟動硬件加速時,系統(tǒng)會使用軟件方式來渲染UI刃宵,即上圖中的軟件繪制衡瓶。
image.png
整體流程是,系統(tǒng)遍歷Window中的每個View牲证,執(zhí)行其onDraw方法哮针,在onDraw方法中,通過傳入的Canvas對象,執(zhí)行各自的繪制邏輯十厢。這個Canvas對象是通過Window關聯(lián)的Surface的lock函數(shù)獲取的等太,Canvas可以理解為Skia底層接口的封裝。View的繪制工作完成后蛮放,通過Canvas以及Skia將繪制內(nèi)容柵格化到Graphic Buffer(也就是前面提到的Offscreen Buffer)中缩抡。然后經(jīng)過Swap Buffer過程后,把Front Buffer里的內(nèi)容交給SurfaceFlinger包颁,最后由硬件合成器Hardware Composer合成并放入Frame Buffer瞻想,最終輸出到屏幕上。

硬件加速繪制

在軟件繪制過程中娩嚼,由于CPU對圖形計算的處理不是那么高效蘑险,這個過程完全沒有利用到GPU在圖形處理方面的優(yōu)勢。所以在Android 3.0之后岳悟,Android開始支持硬件加速繪制佃迄。到Android 4.0時,系統(tǒng)會默認開啟硬件加速贵少。
image.png

硬件加速繪制與軟件繪制的整個流程差異很大呵俏,最核心的就是硬件加速繪制是通過OpenGL ES的接口調(diào)用,由GPU完成柵格化操作春瞬,然后將繪制內(nèi)容放入Graphic Buffer柴信。此外硬件加速繪制還引入了DisplayList的概念。每個View內(nèi)部都有一個DisplayList宽气,當某個View需要重繪(比如調(diào)用invalidate方法)時,將DisplayList標記為Dirty潜沦。這樣當頁面刷新時萄涯,就只需要重繪一個View的DisplayList,而不是像軟件繪制那樣觸發(fā)整個視圖層級的遍歷唆鸡。從而減少了需要重繪的View的數(shù)量涝影,提高了繪制渲染的效率。
image.png

Peoject Butter

Google 在 2012 年的 I/O 大會上宣布了Project Butter黃油計劃争占,在Android4.1上燃逻,對Android Display系統(tǒng)進行了重構,引入了三個核心要素:VSYNC臂痕、Tripe Buffer和Choreographer伯襟。

VSYNC

VSYNC信號,可以理解為一種定時中斷握童,是一種在PC上已經(jīng)很早就廣泛使用的技術姆怪。由系統(tǒng)底層控制VSYNC信號的發(fā)送頻率。由于大部分屏幕硬件的刷新頻率都是60FPS,所以VSYNC信號的發(fā)送頻率也是60FPS稽揭,即系統(tǒng)底層16.6ms發(fā)出一個VSYNC信號俺附。每次收到VSYNC信號,CPU會立即開始計算需要繪制的數(shù)據(jù)(可以理解為執(zhí)行View的measure溪掀、layout事镣、draw的過程),這也是應用的頁面下一幀繪制的開始揪胃,然后由GPU對數(shù)據(jù)進行柵格化并填充到Offscreen Buffer蛮浑。收到VSYNC信號的同時,SurfaceFlinger開始收集每一個Surface的Front Buffer中的內(nèi)容只嚣,交給Hardware Composer進行合成并輸出到Frame Buffer沮稚,最終完成屏幕刷新。也就是說系統(tǒng)每次發(fā)出VSYNC信號時册舞,CPU開始進行下一幀繪制的準備蕴掏,最終由GPU完成下一幀顯示內(nèi)容的繪制處理。而SurfaceFlinger則完成的是當前這一幀內(nèi)容的顯示操作调鲸。這就是雙緩沖機制的原理盛杰。
image.png
Tripe Buffer

Tripe Buffer,即三重緩沖機制藐石,三個Graphic Buffer即供。如果你理解了雙緩沖機制的原理,就可以想象一下這樣一個問題于微。當前頁面正在顯示Front Buffer的內(nèi)容逗嫡,GPU正在往Offscreen Buffer填充下一幀的顯示內(nèi)容,而在GPU進行這一操作時株依,會將Offscreen Buffer鎖定驱证,如果CPU和GPU的繪制處理過程耗時太長,超過了一個VSYNC信號周期恋腕,就會導致本該進行Swap Buffer操作時抹锄,因為Offscreen Buffer被鎖定,無法正常進行Swap Buffer荠藤,從而導致Front Buffer里的內(nèi)容也不能更新伙单,還是保留上一幀的內(nèi)容。從而出現(xiàn)丟幀現(xiàn)象哈肖。
image.png

如果再提供一個緩沖區(qū)吻育,CPU、GPU 和顯示設備都能使用各自的緩沖區(qū)工作牡彻,互不影響扫沼。簡單來說出爹,三緩沖機制就是在雙緩沖機制基礎上增加了一個 Graphic Buffer 緩沖區(qū),這樣可以最大限度的利用VSYNC信號周期的空閑時間缎除,帶來的壞處是多使用了一個 Graphic Buffer 所占用的內(nèi)存严就。
image.png
ChoreoGrapher

ChoreoGrapher,主要作用是接受VSYNC信號器罐。系統(tǒng)發(fā)出VSYNC信號的頻率是60FPS梢为,那是不是意味著,不管App的頁面是否需要刷新轰坊,都會接收VSYNC信號铸董,然后開始由CPU準備下一幀繪制需要的數(shù)據(jù)呢?其實不然肴沫,如果App的頁面不需要刷新粟害,App就不會接收到VSYNC信號。只有當頁面需要刷新時颤芬,才會由ChoreoGrapher來執(zhí)行一個類似注冊監(jiān)聽VSYNC信號的操作悲幅,然后當系統(tǒng)下一次發(fā)出VSYNC信號時,ChoreoGrapher就會接收到VSYNC信號站蝠,來執(zhí)行頁面繪制的相關工作汰具。可能這樣的說法有點抽象菱魔,接下來通過介紹ChoreoGrapher的相關源碼來解釋說明它的作用留荔。
了解View的繪制機制的同學都知道,當頁面有視圖變化澜倦,需要刷新時聚蝶,都會執(zhí)行到ViewRootImpl的requestLayoiut方法,很多介紹View繪制原理的文章肥隆,都以requestLayout方法作為頁面繪制過程真正的開始點既荚。它內(nèi)部又會調(diào)用scheduleTraversals方法。

//ViewRootImpl.java
@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            //mLayoutRequested用來輔助判斷是否需要執(zhí)行View的measure和layout過程
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            //向主線程的Messagequeue中添加同步屏障栋艳,目的是讓頁面繪制的相關任務能盡快執(zhí)行
            //頁面繪制的相關任務是以異步消息的方式發(fā)到主線程,在添加同步屏障之后句各,異步消息的任務將優(yōu)先執(zhí)行
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

在scheduleTraversals方法中吸占,我們看到了mChoreographer。此處調(diào)用了它的postCallback方法凿宾。第一個參數(shù)表示回調(diào)類型矾屯,第二個參數(shù)表示一個待執(zhí)行的任務。

//Choreographer.java
@TestApi
    public void postCallback(int callbackType, Runnable action, Object token) {
        postCallbackDelayed(callbackType, action, token, 0);
    }
@TestApi
    public void postCallbackDelayed(int callbackType,
            Runnable action, Object token, long delayMillis) {
        if (action == null) {
            throw new IllegalArgumentException("action must not be null");
        }
        if (callbackType < 0 || callbackType > CALLBACK_LAST) {
            throw new IllegalArgumentException("callbackType is invalid");
        }

        postCallbackDelayedInternal(callbackType, action, token, delayMillis);
    }
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);
            }
        }
    }

如上述代碼所示初厚,經(jīng)過層層調(diào)用件蚕,完成實際工作的是postCallbackDelayedInternal方法孙技,主要完成兩件事:
第一,將postCallback方法傳進來的待執(zhí)行任務排作,封裝成一個Callback牵啦,保存在mCallbackQueues隊列中,mCallbackQueues是CallbackQueue類型的數(shù)組妄痪。CallbackQueue是Choreographer的內(nèi)部類哈雏,其實質(zhì)是一個單向鏈表,其中的每一個Callback按dueTime的先后排序衫生。

//Choreographer#CallbackQueue
@UnsupportedAppUsage
        public void addCallbackLocked(long dueTime, Object action, Object token) {
            CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);
            CallbackRecord entry = mHead;
            if (entry == null) {
                mHead = callback;
                return;
            }
            if (dueTime < entry.dueTime) {
                callback.next = entry;
                mHead = callback;
                return;
            }
            while (entry.next != null) {
                if (dueTime < entry.next.dueTime) {
                    callback.next = entry.next;
                    break;
                }
                entry = entry.next;
            }
            entry.next = callback;
        }

第二件事就是向主線程中添加一個MSG_DO_SCHEDULE_VSYNC類型的異步消息裳瘪,根據(jù)duetime的時序判斷是直接添加還是在指定的dutime時間添加,而添加MSG_DO_SCHEDULE_VSYNC消息的工作就是在scheduleFrameLocked方法中完成

//Choreographer.java
private void scheduleFrameLocked(long now) {
        if (!mFrameScheduled) {
            // mFrameScheduled保證16ms內(nèi)罪针,只會申請一次垂直同步信號
            // scheduleFrameLocked可以被調(diào)用多次彭羹,但是mFrameScheduled保證下一個vsync到來之前,不會有新的請求發(fā)出
            // 多余的scheduleFrameLocked調(diào)用被無效化
            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);
            }
        }
    }

首先泪酱,需要注意的是mFrameScheduled派殷,它是當前Choreographer是否正在監(jiān)聽VSYNC的標識,同時也能防止重復監(jiān)聽西篓,Choreographer每次接收到VSYNC信號時愈腾,將mFrameScheduled置為false,當需要監(jiān)聽VSYNC信號時岂津,再將mFrameScheduled置為true虱黄。這就意味著Choreographer每次接收到VSYNC信號,處理完后續(xù)邏輯吮成,之后監(jiān)聽下一個VSYNC信號時橱乱,需要重新注冊。
根據(jù)代碼中的注釋粱甫,方法開頭的USE_VSYNC用來區(qū)分是否啟用了vysnc機制泳叠,默認為true。我們主要關注的也是USE_VSYNC為true的情況茶宵。同樣根據(jù)代碼中的注釋危纫,如果當前線程是主線程就直接執(zhí)行scheduleVsyncLocked方法,否則就通過異步消息的方式乌庶,讓主線程執(zhí)行scheduleVsyncLocked方法

ChoreoGrapher.java
@UnsupportedAppUsage
    private void scheduleVsyncLocked() {
        mDisplayEventReceiver.scheduleVsync();
    }

mDisplayEventReceiver是抽象類DisplayEventReceiver的對象种蝶。它是來定義SYNC信號的接收器。它的主要功能有兩個:“注冊”VSYNC信號的監(jiān)聽和接收VSYNC信號瞒大。它的scheduleVsync方法內(nèi)部會調(diào)用nativeScheduleVsync方法螃征,這個native方法是最終實現(xiàn)VSYNC信號監(jiān)聽的"注冊"。這里不再對nativeScheduleVsync進一步分析透敌,所以這里說的注冊盯滚,并不一定和Android的BroadcasrReceiver機制的注冊一個概念踢械。有興趣的同學可以繼續(xù)深入到native方法中進一步了解。

@UnsupportedAppUsage
    public void scheduleVsync() {
        if (mReceiverPtr == 0) {
            Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
                    + "receiver has already been disposed.");
        } else {
            nativeScheduleVsync(mReceiverPtr);
        }
    }

ChoreoGrapher的內(nèi)部類FrameDisplayEventReceiver魄藕,繼承自DisplayEventReceiver内列,重寫了onVsync方法。而從DisplayEventReceiver源碼的注釋可以得知泼疑,onVsync方法就是接收到VSYNC信號的回調(diào)方法德绿。由于FrameDisplayEventReceiver實現(xiàn)了Runnable接口,可以將其當做是一個可執(zhí)行任務退渗。而FrameDisplayEventReceiver的onVsync方法移稳,就做了一件事,就是向主線程發(fā)送異步消息会油,在主線程中執(zhí)行它的run方法个粱。

//Choreographer#FrameDisplayEventReceiver
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);
        }

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

如上述代碼所示,在主線程中通過執(zhí)行FrameDisplayEventReceiver的run方法翻翩,也就是執(zhí)行Choreographer的doFrame方法都许,其中需要重點關注的相關代碼如下

//Choreographer.java
@UnsupportedAppUsage
    void doFrame(long frameTimeNanos, int frame) {
            ...
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
            AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);

            mFrameInfo.markInputHandlingStart();
            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);

            mFrameInfo.markAnimationsStart();
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
            doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);

            mFrameInfo.markPerformTraversalsStart();
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);

            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
            ...
    }

這個Choreographer.CALLBACK_TRAVERSAL很眼熟,在ViewRootImpl的schduleTraversals方法中調(diào)用Choreographer的postCallback方法時傳入的第一個參數(shù)也是Choreographer.CALLBACK_TRAVERSAL嫂冻。

//Choreographer.java
void doCallbacks(int callbackType, long frameTimeNanos) {
            ...
            callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                    now / TimeUtils.NANOS_PER_MS);
            if (callbacks == null) {
                return;
            }
            ...
            for (CallbackRecord c = callbacks; c != null; c = c.next) {
                if (DEBUG_FRAMES) {
                    Log.d(TAG, "RunCallback: type=" + callbackType
                            + ", action=" + c.action + ", token=" + c.token
                            + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
                }
                c.run(frameTimeNanos);
            }finally {
            synchronized (mLock) {
                mCallbacksRunning = false;
                do {
                    final CallbackRecord next = callbacks.next;
                    recycleCallbackLocked(callbacks);
                    callbacks = next;
                } while (callbacks != null);
            }
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
            ....
}

在doCallbacks方法中胶征,根據(jù)callbackType也就是Choreographer.CALLBACK_TRAVERSAL。取出對應的CallbackQueue桨仿。在schduleTraversals調(diào)用postCallback方法時傳入的第二個參數(shù)mTraversalRunnable睛低,就保存在上述代碼的某一個CallbackRecord中,查看CallbackRecord的run方法服傍,其實就是調(diào)用了封裝在其中的action的run方法钱雷。從以上分析,這里的action吹零,就是在mTraversalRunnable罩抗。分析到這一步,就可以回到ViewRootImpl的代碼中了灿椅。

//ViewRootImpl.java
final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

mTraversalRunnable的run方法套蒂,調(diào)用了doTraversal方法。到此茫蛹,熟悉View的繪制過程的同學泣懊,應該就很清楚接下來的流程了。doTraversal內(nèi)部會調(diào)用performTraversals方法麻惶。在performTraversals方法中會,執(zhí)行當前頁面窗口對應的DecorView的整個視圖層級的繪制流程信夫。

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

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

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

總結(jié)

本文主要介紹了

  • 卡頓和丟幀的概念
  • UI渲染機制的原理窃蹋,其中涉及到的重要組件是Canvas卡啰、Surface、Graphic Buffer警没、和SurfaceFlinger匈辱。
  • 軟件繪制和硬件加速繪制的概念和區(qū)別。CPU和GPU在繪制渲染過程中杀迹,各自完成的工作亡脸。以及Skia庫和OpenGL ES庫的簡單介紹。
  • Project Butter的三要素树酪,VSYNC信號和Triple Buffer的原理浅碾,著重從源碼角度分析了Choreographer的工作機制。

本文參考
Android開發(fā)高手課 UI 優(yōu)化(上):UI 渲染的幾個關鍵概念
Android 屏幕刷新機制

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末续语,一起剝皮案震驚了整個濱河市垂谢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌疮茄,老刑警劉巖滥朱,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異力试,居然都是意外死亡徙邻,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門畸裳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缰犁,“玉大人,你說我怎么就攤上這事躯畴∶窆模” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵蓬抄,是天一觀的道長丰嘉。 經(jīng)常有香客問我,道長嚷缭,這世上最難降的妖魔是什么饮亏? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮阅爽,結(jié)果婚禮上路幸,老公的妹妹穿的比我還像新娘。我一直安慰自己付翁,他們只是感情好简肴,可當我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著百侧,像睡著了一般砰识。 火紅的嫁衣襯著肌膚如雪能扒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天辫狼,我揣著相機與錄音初斑,去河邊找鬼。 笑死膨处,一個胖子當著我的面吹牛见秤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播真椿,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼鹃答,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了瀑粥?” 一聲冷哼從身側(cè)響起挣跋,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎狞换,沒想到半個月后避咆,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡修噪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年查库,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片黄琼。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡樊销,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出脏款,到底是詐尸還是另有隱情围苫,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布撤师,位于F島的核電站剂府,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏剃盾。R本人自食惡果不足惜腺占,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望痒谴。 院中可真熱鬧衰伯,春花似錦、人聲如沸积蔚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至临扮,卻和暖如春论矾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背杆勇。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留饱亿,地道東北人蚜退。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像彪笼,于是被迫代替她去往敵國和親钻注。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,700評論 2 354