Android-Choreographer 垂直同步 Vsync

本文主要講 view.requestLayout() 到 view 重新繪制成功流程恢着。

view.requestLayout 調(diào)用的是 parent.requestLayout,直到 DecorView 最終到 ViewRootImpl.requestLayout 方法袜刷。

提示: requestLayout() 跟 invalidate() 區(qū)別在于 PFLAG_FORCE_LAYOUT鸦难、PFLAG_INVALIDATED侨嘀,invalidate 不會重新測量布局镰惦,只會重新繪制

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            //1. 當(dāng)前調(diào)用線程檢查
            checkThread();
            mLayoutRequested = true;
            //2. 
            scheduleTraversals();
        }
    }
    
    void checkThread() {
        //1.1 調(diào)用線程必須在主線程
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }

    @UnsupportedAppUsage
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            //首先中執(zhí)行同步屏障,msg.target 為空,該 msg 會插入到 messageQueue 合適的位置,
            //當(dāng) messageQueue.next() 發(fā)現(xiàn)有 target 為空的空的消息侥钳,則優(yōu)先找 msg.isAsynchronous() 的消息插隊,此處是為了 vsync 消息優(yōu)先執(zhí)行
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //2.1 主要將 mTraversalRunnable 傳遞給 mChoreographer
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
    }
Choreographer

調(diào)用棧:mChoreographer.postCallback(int callbackType, Runnable action, Object token) --> postCallbackDelayed() --> postCallbackDelayedInternal()

    //參數(shù) action 是 ViewRootImpl 中 mTraversalRunnable
    private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        ...
        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            //2.1.1 Choreographer 將任務(wù)保存在 mCallbackQueues 中柄错,callbackType 為 CALLBACK_TRAVERSAL舷夺,等待 vsync 到來時通過類型回調(diào) runnable
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
            //如果不是延時消息,立即請求 vsync
            if (dueTime <= now) {
                //2.1.2 
                scheduleFrameLocked(now);
            } else {
                ////2.1.3 延時消息售貌,發(fā)送 MSG_DO_SCHEDULE_CALLBACK 的同步消息給 FrameHandler给猾,
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                //設(shè)置為異步消息,結(jié)合第 2 步的 postSyncBarrier 使用颂跨,是的該消息優(yōu)先普通消息執(zhí)行
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

    private void scheduleFrameLocked(long now) {
        if (!mFrameScheduled) {
            mFrameScheduled = true;
            if (USE_VSYNC) {
                ...
                // 2.1.2.1 當(dāng)運行在 Looper 線程敢伸,則立刻調(diào)度 vsync,否則發(fā)送消息到 UI 線程再調(diào)度 vsync
                if (isRunningOnLooperThreadLocked()) {
                    //下面再看該方法
                    scheduleVsyncLocked();
                } else {
                    //// 2.1.2.2 發(fā)送消息到 UI 線程恒削,請求 vsync
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtFrontOfQueue(msg);
                }
            } else {
                // 否則執(zhí)行 doFrame
                Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, nextFrameTime);
            }
        }
    }

    @UnsupportedAppUsage
    private void scheduleVsyncLocked() {
        //通過 FrameDisplayEventReceiver 調(diào)度 vysnc池颈,最終調(diào)用 nativeScheduleVsync() 方法
        mDisplayEventReceiver.scheduleVsync();
    }

至此從調(diào)用 requestLayout 到請求 Vsync 信號過程已經(jīng)結(jié)束。
下面看收到 Vsync 信號后钓丰,如何處理 mTraversalRunnable 任務(wù)躯砰。

FrameDisplayEventReceiver
    private final class FrameDisplayEventReceiver extends DisplayEventReceiver
            implements Runnable {
        private boolean mHavePendingVsync;
        private long mTimestampNanos;
        private int mFrame;
         ...
        //收到 vsync 信號回調(diào)該方法
        @Override
        public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
            ...
             //3.1 FrameDisplayEventReceiver 是一個 Runnable 的 callback,把此對象作為異步消息發(fā)送給 FrameHandler携丁,隨后 handler 會調(diào)用改對象的 run 方法琢歇。
            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;
            //3.2 FrameHandler 執(zhí)行該方法,回到 Choreographer 的 doFrame 方法
            doFrame(mTimestampNanos, mFrame);
        }
    }

回到 Choreographer
    @UnsupportedAppUsage
    void doFrame(long frameTimeNanos, int frame) {
        final long startNanos;
        synchronized (mLock) {
            ...
            //省略梦鉴,主要一計算掉幀邏輯李茫,二是記錄幀繪制信息
            ...
        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
            AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);

            mFrameInfo.markInputHandlingStart();
            //處理多種callback
            //依次是 input 調(diào)用棧,會回調(diào)到 DecorView 的 dispatchTouchEvent
            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);

            mFrameInfo.markAnimationsStart();
            //二是 animation 用棧,
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
            doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);

            ////三是Traversal調(diào)用棧,即最發(fā)送給 Choreographer 的任務(wù) mTraversalRunnable
            mFrameInfo.markPerformTraversalsStart();
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);

            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
        } finally {
            AnimationUtils.unlockAnimationClock();
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        ...
    }

    void doCallbacks(int callbackType, long frameTimeNanos) {
        CallbackRecord callbacks;
        synchronized (mLock) {

            final long now = System.nanoTime();
            //通過 callbackType 取出 callbacks
            callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                    now / TimeUtils.NANOS_PER_MS);
            if (callbacks == null) {
                return;
            }
            mCallbacksRunning = true;
        ...
        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
            for (CallbackRecord c = callbacks; c != null; c = c.next) {
                //調(diào)用 Runnable.run 方法尚揣,mTraversalRunnable run 會調(diào)用 doTraversal() 方法
                c.run(frameTimeNanos);
            }
        } finally {
            ...
        }
    }

doTraversal()方法則是 測量涌矢、布局、繪制 入口快骗,此處不做分析娜庇。

Vsync 垂直同步:
????涉及到垂直刷新脈沖、vsync 方篮、gpu 緩沖區(qū) Frame Buffer名秀、Back Buffer 三重緩存,跟 Choreographer
????gpu 像素柵格化
????垂直同步使得顯卡的輸出幀數(shù)和屏幕的刷新速度保持一致藕溅,其中 vsync 用來同步信息匕得,buffer 緩存數(shù)據(jù),當(dāng) vsync 出現(xiàn)時,cpu 會立即處理下一幀數(shù)據(jù)寫入到緩存中汁掠,
????之后gpu再渲染數(shù)據(jù)寫在同一個緩存中略吨,當(dāng)vsync時,下一幀的 buffer 跟當(dāng)前幀所在的buffer數(shù)據(jù)交換考阱,當(dāng)如果之前幀未顯示完翠忠,是不會進行數(shù)據(jù)交換的。屏幕掃描下一次的數(shù)據(jù)顯示乞榨。
????當(dāng)一個信號來時秽之,假設(shè)a b buffer都被占用,此時gpu使用c緩存下一幀的數(shù)據(jù)吃既,可以有效減少掉幀的幾率考榨。

Choreographer(編舞者)
    FrameHandler 處理 3 各消息類型
        MSG_DO_FRAME:開始渲染下一幀操作
        MSG_DO_SCHEDULE_VSYNC:請求 Vsync 信號
        MSG_DO_SCHEDULE_CALLBACK:請求執(zhí)行 callback
    FrameDisplayEventReceiver 用來接收垂直跟發(fā)送同步信號
總結(jié):

1、view.requestLayout 調(diào)用的是 parent.requestLayout ,直到 DecorView 最終到 ViewRootImpl.requestLayout 方法鹦倚。
2河质、首先判斷正在測量布局,沒有則 checkThread 檢驗當(dāng)前是否在主線程申鱼。在 scheduleTraversals 首先中執(zhí)行同步屏障愤诱,其次再將任務(wù) postCallback 給 Choreographer,Choreographer 將任務(wù)保存在 mCallbackQueues 中捐友,同時發(fā)送 MSG_DO_SCHEDULE_CALLBACK 的同步消息給FrameHandler淫半。FrameHandler 的優(yōu)先執(zhí)行 CALLBACK 同步消息調(diào)用 doScheduleCallback,mCallbackQueues 不為空且 callback 不是延遲執(zhí)行匣砖,調(diào)用 scheduleFrameLocked 方法請求 Vsync 信號科吭。當(dāng)運行在 Looper 線程,則立刻調(diào)度 vsync猴鲫,否則对人,發(fā)送消息到UI線程再調(diào)度 vsync。其中是通過 FrameDisplayEventReceiver 調(diào)度 vysnc拂共。

FrameDisplayEventReceiver 有兩個作用牺弄,一個是 scheduleVsync 請求調(diào)度,另一個是接收 vsync 信號回調(diào) onVsync宜狐,當(dāng)接收到 vsync 信號時势告,調(diào)用doFrame 方法,開始渲染下一幀抚恒。

doFrame 可以分為三步:一是計算掉幀邏輯咱台,二是記錄幀繪制信息,三是處理多種 callback俭驮,依次是 input 調(diào)用棧,會回調(diào)到 DecorView 的 dispatchTouchEvent回溺。
二是 animation 調(diào)用棧,執(zhí)行動畫;三是 Traversal 調(diào)用棧遗遵,即最發(fā)送給 Choreographer 的任務(wù)

doTraversal()->performTraversals() ->
performMeasure():measere():onMeasure()-> 
performLayout():layout():onLayout()->

performDraw():drawSoftware():draw()->drawBackground()->保存當(dāng)前圖層信息(可跳過) ->onDraw()-> dispatchDraw()->繪制View邊緣萍恕、陰影效果(可跳過)->onDrawForeground()裝飾、滾動條.

動畫如何流暢執(zhí)行:調(diào)用animation.start時瓮恭,最終在AnimationHandler會給Choreographer.FrameCallback 回調(diào) doFrame雄坪,里面 post了自己。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末屯蹦,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子绳姨,更是在濱河造成了極大的恐慌登澜,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件飘庄,死亡現(xiàn)場離奇詭異脑蠕,居然都是意外死亡,警方通過查閱死者的電腦和手機跪削,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門谴仙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人碾盐,你說我怎么就攤上這事晃跺。” “怎么了毫玖?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵掀虎,是天一觀的道長。 經(jīng)常有香客問我付枫,道長烹玉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任阐滩,我火速辦了婚禮二打,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘掂榔。我一直安慰自己继效,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布衅疙。 她就那樣靜靜地躺著莲趣,像睡著了一般。 火紅的嫁衣襯著肌膚如雪饱溢。 梳的紋絲不亂的頭發(fā)上喧伞,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天,我揣著相機與錄音,去河邊找鬼潘鲫。 笑死翁逞,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的溉仑。 我是一名探鬼主播挖函,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼浊竟!你這毒婦竟也來了怨喘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤振定,失蹤者是張志新(化名)和其女友劉穎必怜,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體后频,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡梳庆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了卑惜。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片膏执。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖露久,靈堂內(nèi)的尸體忽然破棺而出更米,到底是詐尸還是另有隱情,我是刑警寧澤抱环,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布壳快,位于F島的核電站,受9級特大地震影響镇草,放射性物質(zhì)發(fā)生泄漏眶痰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一梯啤、第九天 我趴在偏房一處隱蔽的房頂上張望竖伯。 院中可真熱鬧,春花似錦因宇、人聲如沸七婴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽打厘。三九已至,卻和暖如春贺辰,著一層夾襖步出監(jiān)牢的瞬間户盯,已是汗流浹背嵌施。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留莽鸭,地道東北人吗伤。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像硫眨,于是被迫代替她去往敵國和親足淆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內(nèi)容