淺談Android的編舞者Choreographer

在ViewRootImpl中有這么個(gè)方法scheduleTraversals伦忠,如果你深入過View的繪制流程,那你應(yīng)該知道就是從這個(gè)方法開始觸發(fā)performTraversals阿蝶,來調(diào)出之后的measure,layout鸯檬,draw隐岛。

  • 初見

    我們先貼出這個(gè)方法:
void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

拋開其他的諸如阻塞handler等代碼,我們直接看```

mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
  • 探索

    我們繼續(xù)跟著這個(gè)方法深究尖昏,看看它到底干了什么:
    postCallback()->postCallbackDelayed()->postCallbackDelayedInternal():
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);
            //delayMillis傳的是0仰税,故此處進(jìn)入條件
            if (dueTime <= now) {
               //實(shí)際上單單從這個(gè)方法的名字我們就能意識(shí)到做的是跟幀有關(guān)的工作。
                scheduleFrameLocked(now);
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

上鎖抽诉,來調(diào)動(dòng)幀的行為陨簇,很有可能就跟界面的一幀一幀的刷新有關(guān),我們接著往下看:

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.
                //是否是主線程掸鹅,一般界面刷新能走到這一步都是在主線程中刷新的塞帐,所以進(jìn)入條件
                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);
            }
        }
    }

這里有個(gè)常量USE_VSYNC拦赠,表示是否允許動(dòng)畫和繪制的垂直同步巍沙,默認(rèn)是為true

// Enable/disable vsync for animations and drawing.
    private static final boolean USE_VSYNC = SystemProperties.getBoolean(
            "debug.choreographer.vsync", true);

緊接著scheduleVsyncLocked()-> mDisplayEventReceiver.scheduleVsync()->nativeScheduleVsync(mReceiverPtr)
走到一個(gè)native方法,底層我就暫時(shí)不分析荷鼠,有興趣的讀者可以去看一下句携,最后會(huì)在底層處理垂直同步,然后回調(diào)onVsync方法允乐,抽象類DisplayEventReceiver并沒有實(shí)現(xiàn)這個(gè)方法矮嫉,我們找到實(shí)現(xiàn)類是FrameDisplayEventReceiver。

@Override
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
    ...

    mTimestampNanos = timestampNanos;
    mFrame = frame;
    Message msg = Message.obtain(mHandler, this);
    msg.setAsynchronous(true);
    mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}

把自身包裝成Message傳給了handler牍疏,調(diào)用其run方法:

@Override
        public void run() {
            mHavePendingVsync = false;
            doFrame(mTimestampNanos, mFrame);
        }
void doFrame(long frameTimeNanos, int frame) {
        final long startNanos;
        synchronized (mLock) {
            ...
            //是否有跳幀蠢笋,如果有那么就打印log并且修正偏差
        }

        //執(zhí)行callback
        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);

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

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

        if (DEBUG_FRAMES) {
            final long endNanos = System.nanoTime();
            Log.d(TAG, "Frame " + frame + ": Finished, took "
                    + (endNanos - startNanos) * 0.000001f + " ms, latency "
                    + (startNanos - frameTimeNanos) * 0.000001f + " ms.");
        }
    }

doFrame方法做的就是渲染下一幀,第一個(gè)痛不塊中就是去檢測(cè)是否卡頓并修補(bǔ)卡頓鳞陨。然后開始做渲染工作昨寞,我們看幾個(gè)doCallbacks方法的參數(shù):
CALLBACK_INPUT:輸入
CALLBACK_ANIMATION:動(dòng)畫
CALLBACK_TRAVERSAL:遍歷,執(zhí)行measure厦滤、layout援岩、draw
CALLBACK_COMMIT:遍歷完成的提交操作,用來修正動(dòng)畫啟動(dòng)時(shí)間

  會(huì)從上往下的去執(zhí)行一次
void doCallbacks(int callbackType, long frameTimeNanos) {
        CallbackRecord callbacks;
        synchronized (mLock) {
            ...
            final long now = System.nanoTime();
            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) {
                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);
        }
}

Choreographer內(nèi)部維護(hù)了這四種鏈表掏导,渲染每一幀的時(shí)候都會(huì)從上往下的去執(zhí)行相應(yīng)的渲染操作享怀,有輸入那么就先渲染輸入隊(duì)列,有動(dòng)畫就渲染動(dòng)畫趟咆,然后遍歷添瓷,然后提交梅屉。

  • 總結(jié)

    1. 控制外部輸入事件處理,動(dòng)畫執(zhí)行仰坦,UI變化履植,以及提交執(zhí)行都是在同一個(gè)類中做的處理,即是Choreographer悄晃。

    2. 在Choreographer對(duì)象中有四條鏈表玫霎,分別保存著待處理的輸入事件,待處理的動(dòng)畫事件妈橄,待處理的遍歷事件庶近,以及待處理的提交時(shí)間。

    3. 每次執(zhí)行的時(shí)候眷蚓,Choreographer會(huì)根據(jù)當(dāng)前的時(shí)間鼻种,只處理事件鏈表中最后一個(gè)事件,當(dāng)有耗時(shí)操作在主線程時(shí)沙热,事件不能及時(shí)執(zhí)行叉钥,就會(huì)出現(xiàn)所謂的“跳幀”,“卡頓”現(xiàn)象篙贸。

    4. Choreographer的共有方法postCallback(callbackType, Object)是往事件鏈表中放事件的方法投队。而doFrame()是消耗這些事件的方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末爵川,一起剝皮案震驚了整個(gè)濱河市敷鸦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌寝贡,老刑警劉巖扒披,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異圃泡,居然都是意外死亡碟案,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門颇蜡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來价说,“玉大人,你說我怎么就攤上這事澡匪∪廴危” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵唁情,是天一觀的道長(zhǎng)疑苔。 經(jīng)常有香客問我,道長(zhǎng)甸鸟,這世上最難降的妖魔是什么惦费? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任兵迅,我火速辦了婚禮,結(jié)果婚禮上薪贫,老公的妹妹穿的比我還像新娘恍箭。我一直安慰自己,他們只是感情好瞧省,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布扯夭。 她就那樣靜靜地躺著,像睡著了一般鞍匾。 火紅的嫁衣襯著肌膚如雪交洗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天橡淑,我揣著相機(jī)與錄音构拳,去河邊找鬼。 笑死梁棠,一個(gè)胖子當(dāng)著我的面吹牛置森,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播符糊,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼凫海,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了濒蒋?” 一聲冷哼從身側(cè)響起盐碱,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤把兔,失蹤者是張志新(化名)和其女友劉穎沪伙,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體县好,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡围橡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了缕贡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片翁授。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖晾咪,靈堂內(nèi)的尸體忽然破棺而出收擦,到底是詐尸還是另有隱情,我是刑警寧澤谍倦,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布塞赂,位于F島的核電站,受9級(jí)特大地震影響昼蛀,放射性物質(zhì)發(fā)生泄漏宴猾。R本人自食惡果不足惜圆存,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望仇哆。 院中可真熱鬧沦辙,春花似錦、人聲如沸讹剔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)延欠。三九已至撞羽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間衫冻,已是汗流浹背诀紊。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留隅俘,地道東北人邻奠。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像为居,于是被迫代替她去往敵國(guó)和親碌宴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

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