Choreographer源碼理解

Choreographer的作用

1丧慈、負(fù)責(zé)接收和處理 App 的各種更新消息和回調(diào)丈牢,等到 Vsync 到來的時候統(tǒng)一處理删豺。比如集中處理 Input(主要是 Input 事件的處理) 立砸、Animation(動畫相關(guān))戈咳、Traversal(包括 measure网沾、layout癞蚕、draw 等操作) ,判斷卡頓掉幀情況辉哥,記錄 CallBack 耗時等

2桦山、負(fù)責(zé)請求和接收 Vsync 信號。接收 Vsync 事件回調(diào)(通過 FrameDisplayEventReceiver.onVsync )醋旦;請求 Vsync(FrameDisplayEventReceiver.scheduleVsync) .

Choreographer初始化
private Choreographer(Looper looper, int vsyncSource) {
        mLooper = looper;//1
        mHandler = new FrameHandler(looper);//2
        mDisplayEventReceiver = USE_VSYNC
                ? new FrameDisplayEventReceiver(looper, vsyncSource)
                : null;//3
        mLastFrameTimeNanos = Long.MIN_VALUE;

        mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());

        mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];//4
        for (int i = 0; i <= CALLBACK_LAST; i++) {
            mCallbackQueues[i] = new CallbackQueue();
        }
        // b/68769804: For low FPS experiments.
        setFPSDivisor(SystemProperties.getInt(ThreadedRenderer.DEBUG_FPS_DIVISOR, 1));
    }

首先需要一個Looper,然后通過Looper來創(chuàng)建一個Handler對象恒水。載創(chuàng)建一個mDisplayEventReceiver對象,主要是處理Vsync信號的回調(diào)和請求饲齐。最后初始化一個回調(diào)隊列钉凌。

FrameHandler
private final class FrameHandler extends Handler {
        public FrameHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_DO_FRAME:
                    doFrame(System.nanoTime(), 0);//1
                    break;
                case MSG_DO_SCHEDULE_VSYNC:
                    doScheduleVsync();//2
                    break;
                case MSG_DO_SCHEDULE_CALLBACK:
                    doScheduleCallback(msg.arg1);//3
                    break;
            }
        }
    }

1表示開始下一幀的渲染操作

2表示請求Vsync

3表示處理callback

FrameDisplayEventReceiver
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
            implements Runnable {
    
        @Override
        public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
            ....
            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);
        }
    }

該類繼承DisplayEventReceiver,實現(xiàn)其兩個方法為onVsync()和run()捂人,onVsync()主要是處理Vsync信號的回調(diào)御雕,然后將消息設(shè)置成異步,通過我們的FrameHandler發(fā)送出去滥搭。run()則是調(diào)用doFrame()酸纲,表示我們接收到Vsync信號之后,開始下一幀的渲染瑟匆。

onVsync()方法中只是使用mHandler發(fā)送消息到MessageQueue中闽坡,不一定是立刻執(zhí)行,如果MessageQueue中前面有較為耗時的操作,那么就要等完成无午,才會執(zhí)行本次的doFrame()媒役。

前面說了,F(xiàn)rameDisplayEventReceiver是請求和回調(diào)宪迟,那請求時在哪里了酣衷?

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

真正的請求是在DisplayEventReceiver類中的scheduleVsync(),里面調(diào)用的是一個native方法來進(jìn)行請求Vsync信號次泽。DisplayEventReceiver 初始化完成后穿仪,Choreographer 與 SurfaceFlinger 的 Vsync 信號傳遞通道就建立好了,后續(xù)有 Vsync 信號到來時意荤,就可以沿著這條通道啊片,一路回調(diào)到 Java 層的 Choreographer.FrameDisplayEventReceiver.onVsync 方法。

callback類型
//輸入事件玖像,首先執(zhí)行
    public static final int CALLBACK_INPUT = 0;
    //動畫紫谷,第二執(zhí)行
    public static final int CALLBACK_ANIMATION = 1;
    //繪制,第三執(zhí)行
    public static final int CALLBACK_TRAVERSAL = 2;
    //提交捐寥,最后執(zhí)行笤昨,
    public static final int CALLBACK_COMMIT = 3;

callback類型四種,其執(zhí)行的順序為0-》1-》2-》3

執(zhí)行流程

那什么時候會用到Choreographer握恳,通過調(diào)用invalidate()或者requestLayout()方法的時候瞒窒,也就是我們需要UI繪制的時候,需要用到Choreographer乡洼。

上面的兩個方法最終都會走到scheduleTraversals()崇裁。

scheduleTraversals()
void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

首先會設(shè)置一個同步屏障,然后調(diào)用postCallback()方法.postCallback()方法中繼續(xù)調(diào)用postCallbackDelayed()束昵,最終會調(diào)用postCallbackDelayedInternal()方法拔稳。

postCallbackDelayedInternal()
private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long 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);
            }
        }
    }

如果dueTime<=當(dāng)前時間,則會去請求Vsync信號妻怎,否則通過handler發(fā)送一條異步消息到隊列中壳炎。

private void scheduleFrameLocked(long now) {
        if (!mFrameScheduled) {
            mFrameScheduled = true;
            if (USE_VSYNC) {
                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);
                Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, nextFrameTime);
            }
        }
    }

1、如果系統(tǒng)未開啟 VSYNC 機(jī)制逼侦,此時直接發(fā)送 MSG_DO_FRAME 消息到 FrameHandler匿辩,此時直接執(zhí)行 doFrame 方法。

2榛丢、Android 4.1 之后系統(tǒng)默認(rèn)開啟 VSYNC铲球,在 Choreographer 的構(gòu)造方法會創(chuàng)建一個 FrameDisplayEventReceiver,scheduleVsyncLocked 方法將會通過它申請 VSYNC 信號晰赞。

3稼病、isRunningOnLooperThreadLocked 方法选侨,其內(nèi)部根據(jù) Looper 判斷是否在原線程,否則發(fā)送消息到 FrameHandler然走。最終還是會調(diào)用 scheduleVsyncLocked 方法申請 VSYNC 信號援制。

請求Vsync信號成功之后,就會走onVsync()回調(diào)芍瑞。onVsync()中晨仑,將接收器本身作為runnable傳入異步消息msg,并使用mHandler發(fā)送msg拆檬,最終執(zhí)行的就是doFrame()方法洪己。

doFrame()
void doFrame(long frameTimeNanos, int frame) {
        final long startNanos;
        synchronized (mLock) {
            long intendedFrameTimeNanos = frameTimeNanos;
            startNanos = System.nanoTime();
            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.");
                }
                final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
                if (DEBUG_JANK) {
                    Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
                            + "which is more than the frame interval of "
                            + (mFrameIntervalNanos * 0.000001f) + " ms!  "
                            + "Skipping " + skippedFrames + " frames and setting frame "
                            + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
                }
                frameTimeNanos = startNanos - lastFrameOffset;
            }

            if (frameTimeNanos < mLastFrameTimeNanos) {
                if (DEBUG_JANK) {
                    Log.d(TAG, "Frame time appears to be going backwards.  May be due to a "
                            + "previously skipped frame.  Waiting for next vsync.");
                }
                scheduleVsyncLocked();
                return;
            }

            if (mFPSDivisor > 1) {
                long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
                if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
                    scheduleVsyncLocked();
                    return;
                }
            }

            mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
            mFrameScheduled = false;
            mLastFrameTimeNanos = frameTimeNanos;
        }

        try {
            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);
        } finally {
            AnimationUtils.unlockAnimationClock();
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

在doFrame方法中,主要做了以下的事:

1竟贯、計算掉幀邏輯

Choreographer.doFrame 的掉幀檢測比較簡單答捕,Vsync 信號到來的時候會標(biāo)記一個 start_time ,執(zhí)行 doFrame 的時候標(biāo)記一個 end_time 屑那,這兩個時間差就是 Vsync處理時延拱镐,也就是掉幀。
這種方法計算的掉幀齐莲,是前一幀的掉幀情況痢站,而不是這一幀的掉幀情況磷箕,這個計算方法是有缺陷的选酗,會導(dǎo)致有的掉幀沒有被計算到

2、記錄幀繪制信息

Choreographer 中 FrameInfo 來負(fù)責(zé)記錄幀的繪制信息岳枷,doFrame 執(zhí)行的時候芒填,會把每一個關(guān)鍵節(jié)點(diǎn)的繪制時間記錄下來,我們使用 dumpsys gfxinfo 就可以看到空繁。

3殿衰、執(zhí)行 CALLBACK_INPUT、CALLBACK_ANIMATION盛泡、CALLBACK_TRAVERSAL闷祥、CALLBACK_COMMIT

最后執(zhí)行的是c.run(frameTimeNanos);

public void run(long frameTimeNanos) {
            if (token == FRAME_CALLBACK_TOKEN) {
                ((FrameCallback)action).doFrame(frameTimeNanos);
            } else {
                ((Runnable)action).run();
            }
        }

最后調(diào)用的是run();

現(xiàn)在我們再來看scheduleTraversals()

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            
        }
    }

((Runnable)action).run()其實執(zhí)行的是mTraversalRunnable傲诵。

mTraversalRunnable()
final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    
    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            performTraversals();//1
        }
    }

從1可以知道凯砍,熟悉view的繪制流程的就知道在performTraversals()中會去執(zhí)行Measure(),Layout(),Draw(),就這樣就形成了一個閉環(huán)拴竹。

vsync.png
小結(jié)

1悟衩、Choreographer 是線程單例的,而且必須要和一個 Looper 綁定栓拜,因為其內(nèi)部有一個 Handler 需要和 Looper 綁定座泳,一般是 App 主線程的 Looper 綁定惠昔。

2、DisplayEventReceiver 是一個 abstract class挑势,其 JNI 的代碼部分會創(chuàng)建一個IDisplayEventConnection 的 Vsync 監(jiān)聽者對象镇防。這樣,來自 AppEventThread 的 VSYNC 中斷信號就可以傳遞給 Choreographer 對象了潮饱。當(dāng) Vsync 信號到來時营罢,DisplayEventReceiver 的 onVsync 函數(shù)將被調(diào)用。

3饼齿、DisplayEventReceiver 還有一個 scheduleVsync 函數(shù)饲漾。當(dāng)應(yīng)用需要繪制UI時,將首先申請一次 Vsync 中斷缕溉,然后再在中斷處理的 onVsync 函數(shù)去進(jìn)行繪制考传。

4、Choreographer 定義了一個 FrameCallback interface证鸥,每當(dāng) Vsync 到來時僚楞,其 doFrame 函數(shù)將被調(diào)用。這個接口對 Android Animation 的實現(xiàn)起了很大的幫助作用枉层。以前都是自己控制時間泉褐,現(xiàn)在終于有了固定的時間中斷。

5鸟蜡、Choreographer 的主要功能是膜赃,當(dāng)收到 Vsync 信號時,去調(diào)用使用者通過 postCallback 設(shè)置的回調(diào)函數(shù)揉忘。目前一共定義了四種類型的回調(diào)跳座,它們分別是:

CALLBACK_INPUT : 處理輸入事件處理有關(guān)
CALLBACK_ANIMATION : 處理 Animation 的處理有關(guān)
CALLBACK_TRAVERSAL : 處理和 UI 等控件繪制有關(guān)
CALLBACK_COMMIT : 處理 Commit 相關(guān)回調(diào)

6、CALLBACK_INPUT 泣矛、CALLBACK_ANIMATION 會修改 view 的屬性疲眷,所以要先于 CALLBACK_TRAVERSAL 執(zhí)行

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市您朽,隨后出現(xiàn)的幾起案子狂丝,更是在濱河造成了極大的恐慌,老刑警劉巖哗总,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件几颜,死亡現(xiàn)場離奇詭異,居然都是意外死亡魂奥,警方通過查閱死者的電腦和手機(jī)菠剩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來耻煤,“玉大人具壮,你說我怎么就攤上這事准颓。” “怎么了棺妓?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵攘已,是天一觀的道長。 經(jīng)常有香客問我怜跑,道長样勃,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任性芬,我火速辦了婚禮峡眶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘植锉。我一直安慰自己辫樱,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布俊庇。 她就那樣靜靜地躺著狮暑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪辉饱。 梳的紋絲不亂的頭發(fā)上搬男,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天伦意,我揣著相機(jī)與錄音禀倔,去河邊找鬼敌蚜。 笑死缰趋,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的伏穆。 我是一名探鬼主播耻卡,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼挺益!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起乘寒,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤望众,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后伞辛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體烂翰,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年蚤氏,在試婚紗的時候發(fā)現(xiàn)自己被綠了甘耿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡竿滨,死狀恐怖佳恬,靈堂內(nèi)的尸體忽然破棺而出捏境,到底是詐尸還是另有隱情,我是刑警寧澤毁葱,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布垫言,位于F島的核電站,受9級特大地震影響倾剿,放射性物質(zhì)發(fā)生泄漏筷频。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一前痘、第九天 我趴在偏房一處隱蔽的房頂上張望凛捏。 院中可真熱鬧,春花似錦芹缔、人聲如沸葵袭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坡锡。三九已至,卻和暖如春窒所,著一層夾襖步出監(jiān)牢的瞬間鹉勒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工吵取, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留禽额,地道東北人。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓皮官,卻偏偏與公主長得像脯倒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子捺氢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評論 2 351