Android 之 Choreographer 詳細分析

UI 優(yōu)化系列專題,來聊一聊 Android 渲染相關知識,主要涉及 UI 渲染背景知識如何優(yōu)化 UI 渲染兩部分內(nèi)容。


UI 優(yōu)化系列專題
  • UI 渲染背景知識

View 繪制流程之 setContentView() 到底做了什么?
View 繪制流程之 DecorView 添加至窗口的過程
深入 Activity 三部曲(3)View 繪制流程
Android 之 LayoutInflater 全面解析
關于渲染,你需要了解什么?
Android 之 Choreographer 詳細分析

  • 如何優(yōu)化 UI 渲染

Android 之如何優(yōu)化 UI 渲染(上)
Android 之如何優(yōu)化 UI 渲染(下)


Android 的 UI 渲染性能是 Google 長期以來非常重視的,基本每次 Google I/O 都會花很多篇幅講這一塊。不過隨著 Android 系統(tǒng)的不斷演進和完善,時至今日,關于 Android UI 卡頓的話題也越來越少。

Google 在 2012 年的 I/O 大會上宣布了 Project Butter 計劃,那個曾經(jīng)嚴重影響 Android 口碑的 UI 流程性問題,首先在這得到有效的控制。并且在 Android 4.1 中正式開啟了這個機制。

Project Butter 主要包含三個組成部分:VSYNC、Triple Buffering 和今天要重點分析的 Choreographer。關于 Project Butter 的詳細介紹,你可以參考這里

Choreographer

Choreographer 是 Android 4.1 新增的機制,用于配合系統(tǒng)的 VSYNC 中斷信號。它本質(zhì)是一個 Java 類,如果直譯的話為舞蹈指導,看到這個詞不得不贊嘆設計者除了 Coding 之外的廣泛視野。舞蹈是有節(jié)奏的,節(jié)奏使舞蹈的每個動作更加協(xié)調(diào)和連貫;視圖刷新也是如此,Choreographer 可以接收系統(tǒng)的 VSYNC 信號,統(tǒng)一管理應用的輸入、動畫和繪制等任務的執(zhí)行時機。業(yè)界一般通過它來監(jiān)控應用的幀率。

我們先從 Choreographer 的構造方法入手,看看 Choreographer 是如何協(xié)調(diào)任務的執(zhí)行。

private Choreographer(Looper looper, int vsyncSource) {
        // 當前線程的Looper
        mLooper = looper;
        // 使用該Looper創(chuàng)建FrameHandler
        mHandler = new FrameHandler(looper);
        // 是否開啟VSYNC,開啟VSYNC后將通過FrameDisplayEventReceiver接收VSYNC脈沖信號
        mDisplayEventReceiver = USE_VSYNC
                ? new FrameDisplayEventReceiver(looper, vsyncSource)
                : null;
        mLastFrameTimeNanos = Long.MIN_VALUE;

        // 計算一幀的時間
        // Android手機屏幕采用60Hz的刷新頻率
        // 這里是納秒 ≈16000000ns 還是16ms
        mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
        // 創(chuàng)建一個CallbackQueu的數(shù)組,默認為4
        // CallbackQueue中存放要執(zhí)行的輸入、動畫、遍歷繪制等任務
        // 也就是 CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL
        mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
        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));
 }
  1. Choreographer 的構造方法被設計成私有,并且是線程單例的。只能通過其內(nèi)部的 getInstance 方法獲取當前線程的 Choreographer 實例:
public static Choreographer getInstance() {
    // Choreographer線程單例的實現(xiàn)方式
    return sThreadInstance.get();
}

通過 ThreadLocal 實現(xiàn) Choreographer 的線程單例。

private static final ThreadLocal<Choreographer> sThreadInstance =
            new ThreadLocal<Choreographer>() {
        @Override
        protected Choreographer initialValue() {
            // 獲取當前線程的Looper對象
            Looper looper = Looper.myLooper();
            if (looper == null) {
                // 如果當前線程未創(chuàng)建Looper對象則拋出異常
                // 主線程(UI線程)的Looper默認在ActivityThread的main方法被創(chuàng)建
                throw new IllegalStateException("The current thread must have a looper!");
            }
            // 為當前線程創(chuàng)建一個Choreographer對象
            Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
            if (looper == Looper.getMainLooper()) {
                // 如果是UI線程賦值給成員mMainInstance
                mMainInstance = choreographer;
            }
            return choreographer;
        }
};
  1. Choreographer 的構造必須傳遞一個 Looper 對象,其內(nèi)部會根據(jù)該 Looper 創(chuàng)建一個 FrameHandler。Choreographer 的所有任務最終都會發(fā)送到該 Looper 所在的線程。
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:
                    // 執(zhí)行doFrame
                    // 如果啟用VSYNC機制,當VSYNC信號到來時觸發(fā)
                    doFrame(System.nanoTime(), 0);
                    break;
                case MSG_DO_SCHEDULE_VSYNC:
                    // 申請VSYNC信號,例如當前需要繪制任務時
                    doScheduleVsync();
                    break;
                case MSG_DO_SCHEDULE_CALLBACK:
                    // 需要延遲的任務,最終還是執(zhí)行上述兩個事件
                    doScheduleCallback(msg.arg1);
                    break;
            }
        }
}
  1. 注意 USE_VSYNC,用于判斷當前是否啟用 VSYNC 機制,Android 在 4.1 之后默認開啟該機制。
private static final boolean USE_VSYNC = SystemProperties.getBoolean(
            "debug.choreographer.vsync", true);

FrameDisplayEventReceiver 是 DisplayEventReceiver 的子類,DisplayEventReceiver 是一個 abstract class。在 DisplayEventReceiver 的構造方法會通過 JNI 創(chuàng)建一個 IDisplayEventConnection 的 VSYNC 的監(jiān)聽者。

public DisplayEventReceiver(Looper looper, int vsyncSource) {
        if (looper == null) {
            throw new IllegalArgumentException("looper must not be null");
        }

        mMessageQueue = looper.getQueue();
        // 注冊VSYNC信號監(jiān)聽者
        mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue,
                vsyncSource);

        mCloseGuard.open("dispose");
    }

另外 DisplayEventReceiver 內(nèi)還包括用于申請 VSYNC 信號的 scheduledVsync 方法,

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 {
            // 申請VSYNC中斷信號
            // 會回調(diào)onVsync方法
            nativeScheduleVsync(mReceiverPtr);
        }
}

和用于接收 VSYNC 信號的 onVsync 方法。這樣,當應用需要繪制時,通過 scheduledVsync 方法申請 VSYNC 中斷,來自 EventThread 的 VSYNC 信號就可以傳遞到 Choreographer:

public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
        // 該方法在其子類FrameDisplayEventReceiver中被重寫
        // 目的是通知Choreographer
}
  1. CallbackQueue,用于保存通過 postCallback 添加的任務。目前一共定義了四種任務類型,它們分別是:
  • CALLBACK_INPUT:優(yōu)先級最高,和輸入事件處理有關。
  • CALLBACK_ANIMATION:優(yōu)先級其次,和 Animation 的處理有關
  • CALLBACK_TRAVERSAL:優(yōu)先級最低,和 UI 繪制任務有關
  • CALLBACK_COMMIT:最后執(zhí)行,和提交任務有關(在 API Level 23 添加)

優(yōu)先級的高低和處理順序有關,每當收到 VSYNC 信號時,Choreographer 將首先處理 INPUT 類型的任務,然后是 ANIMATION 類型,最后才是 TRAVERSAL 類型。

通過 Choreographer 添加的任務最后都被封裝成 CallbackRecord,同種任務之間按照時間順序以鏈表的形式保存在 CallbackQueue 內(nèi)。

private static final class CallbackRecord {
        // 鏈表,指向下一個
        public CallbackRecord next;
        // 到期時間
        public long dueTime;
        // Runnable or FrameCallback
        public Object action;

        public Object token;

        public void run(long frameTimeNanos) {
            if (token == FRAME_CALLBACK_TOKEN) {
                // 通過postFrameCallback 或 postFrameCallbackDelayed
                // 會執(zhí)行這里
                ((FrameCallback)action).doFrame(frameTimeNanos);
            } else {
                ((Runnable)action).run();
            }
        }
 }

CallbackQueue 是一個容量為 4 的數(shù)組,分別對應不同的任務類型。


接下來,以 View 的繪制流程為例,從 ViewRootImpl 的 scheduleTraversals 方法開始,其內(nèi)部通過 Choreographer 的 postCallback 將繪制任務添加到 Chorographer。關于 View 繪制流程的詳細分析,可以參考《View 繪制流程之 DecorView 添加至窗口的過程》和《深入 Activity 三部曲(3)之 View 繪制流程》。

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            // 同步屏障,阻塞所有的同步消息
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            // 注意mTraversaRunnable是一個Runnable對象
            // 通過 Choreographer 發(fā)送繪制任務
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
           // ...
        }
    }

Choreographer 是線程單例的,大家是否還記得 Android 系統(tǒng)的 Looper 對象也是 線程單例。主線程 Looper 是在 ActivityThread 的 main 方法被創(chuàng)建。如果要在子線程使用 Handler,必須先為其創(chuàng)建一個 Looper 實例。

Choreographer 提供了兩種添加任務的方式,postCallback() 和 postFrameCallback(),當然還有對應的 delay 方法。

  • postCallback 對應調(diào)用 postCallbackDelayed
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");
        }
        // 最終都會調(diào)用到postCallbackDelayedInternal
        postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}
  • postFrameCallback 對應調(diào)用 postFrameCallbackDelayed
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
        if (callback == null) {
            throw new IllegalArgumentException("callback must not be null");
        }

        //最終調(diào)用postCallbackDelayedInternal
        postCallbackDelayedInternal(CALLBACK_ANIMATION,
                callback, FRAME_CALLBACK_TOKEN, delayMillis);
 }

postCallback 相比 postFrameCallback 更加靈活一些。

它們最終都會調(diào)用到 postCallbackDelayedInternal 方法:

private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {

        synchronized (mLock) {
            // 當前時間
            final long now = SystemClock.uptimeMillis();
            // 加上延遲時間
            final long dueTime = now + delayMillis;
            // 根據(jù)任務類型添加到mCallbackQueues中
            // VSYNC信號處理任務具有優(yōu)先級
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

            if (dueTime <= now) {
                //表示立即執(zhí)行,立即申請VSYNC信號
                scheduleFrameLocked(now);
            } else {
                // 在指定時間運行,最終仍然會調(diào)用scheduleFrameLocked
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                // 到時根據(jù)callbackType在mCallbackQueues中查找執(zhí)行
                msg.arg1 = callbackType;
                // 消息設置為異步
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
}

根據(jù)任務類型 callbackType 添加到對應的 CallbackQueue 內(nèi),然后判斷任務是否有延遲,無延遲則立即執(zhí)行 scheduleFrameLocked 方法,否則發(fā)送定時消息到 FrameHandler,不過其最終還是調(diào)用到 scheduleFrameLocked 方法:

private void scheduleFrameLocked(long now) {
        //mFrameScheduled默認為false
        if (!mFrameScheduled) {
            mFrameScheduled = true;
            // 判斷是否開啟VSYNC
            if (USE_VSYNC) {
                // 判斷是否在原線程
                if (isRunningOnLooperThreadLocked()) {
                    //默認會走這里
                    scheduleVsyncLocked();
                } else {
                    // 否則不在原線程,發(fā)送消息到原線程
                    // 最后還是調(diào)用scheduleVsyncLocked方法
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtFrontOfQueue(msg);
                }
            } else {
                // 如果未開啟VSYNC則直接doFrame方法
                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);
            }
        }
}

注意 USE_VSYNC,如果系統(tǒng)未開啟 VSYNC 機制,此時直接發(fā)送 MSG_DO_FRAME 消息到 FrameHandler。注意查看上面貼出的 FrameHandler 代碼,此時直接執(zhí)行 doFrame 方法。

不過 Android 4.1 之后系統(tǒng)默認開啟 VSYNC,還記得在 Choreographer 的構造方法會創(chuàng)建一個 FrameDisplayEventReceiver,scheduleVsyncLocked 方法將會通過它申請 VSYNC 信號。

  • 這里注意 isRunningOnLooperThreadLocked 方法,其內(nèi)部根據(jù) Looper 判斷是否在原線程,否則發(fā)送消息到 FrameHandler。最終還是會調(diào)用 scheduleVsyncLocked 方法申請 VSYNC 信號。

通過 FrameDisplayEventReceiver 申請 VSYNC 信號的過程如下:

private void scheduleVsyncLocked() {
    // 調(diào)用 FrameDisplayEventReceiver 的scheduleVsync
   // 實際調(diào)用到其父類DisplayEventReceiver
    mDisplayEventReceiver.scheduleVsync();
}

前面我們也有說過,申請 VSYNC 信號實際是在其父類 DisplayEventReceiver。

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 {
            // 申請VSYNC信號
            nativeScheduleVsync(mReceiverPtr);
        }
}

接著看下 VSYNC 信號的接收方法 onVsync,該方法在其子類 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);
        }

        @Override
        public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
            if (builtInDisplayId != SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {              
                // 忽略來自非主屏的VSYNC信號
                scheduleVsync();
                return;
            }

             // ... 省略

            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;
            // 發(fā)送消息執(zhí)行doFrame
            // 注意this,表示當前Runnable
            Message msg = Message.obtain(mHandler, this);
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
        }

        @Override
        public void run() {
            mHavePendingVsync = false;
            // 回調(diào)這里,執(zhí)行doFrame方法
            doFrame(mTimestampNanos, mFrame);
        }
}

FrameDisplayEventReceiver 實現(xiàn)了 Runnable,將其作為 callback 發(fā)送到 FrameHandler,此時 run 方法便得到執(zhí)行并且執(zhí)行 doFrame 方法:

void doFrame(long frameTimeNanos, int frame) {
        final long startNanos;
        synchronized (mLock) {
            if (!mFrameScheduled) {
                // 不是在執(zhí)行Frame任務直接return
                return;
            }

            // ... 省略

            // 預期執(zhí)行時間
            long intendedFrameTimeNanos = frameTimeNanos;
            // 當前時間
            startNanos = System.nanoTime();
            final long jitterNanos = startNanos - frameTimeNanos;
            // 超時時間是否超過一幀的時間
            if (jitterNanos >= mFrameIntervalNanos) {
                // 計算掉幀數(shù)
                final long skippedFrames = jitterNanos / mFrameIntervalNanos;
                // 掉幀超過30幀打印Log提示
                if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
                    // 著名的掉幀Log
                    Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
                            + "The application may be doing too much work on its main thread.");
                }
                final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;

                frameTimeNanos = startNanos - lastFrameOffset;
            }

            if (frameTimeNanos < mLastFrameTimeNanos) {
                // 未知原因,居然小于最后一幀的時間
                // 重新申請VSYNC信號
                scheduleVsyncLocked();
                return;
            }

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

            mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
            // Frame標志位恢復
            mFrameScheduled = false;
            // 記錄最后一幀時間
            mLastFrameTimeNanos = frameTimeNanos;
        }

        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
            AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);

            mFrameInfo.markInputHandlingStart();
            // 先執(zhí)行CALLBACK_INPUT任務
            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);

            mFrameInfo.markAnimationsStart();
            // 再執(zhí)行CALLBACK_ANIMATION
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);

            mFrameInfo.markPerformTraversalsStart();
            // 其次執(zhí)行CALLBACK_TRAVERSAL
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
            // API Level 23 之后加入,
            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
        } finally {
            AnimationUtils.unlockAnimationClock();
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
}
  1. 注意第一個 if 語句,不知道大家是否在自己項目的 Logcat 臺遇到過這樣一條日志:
Skipped (該值>=30) frames!  The application may be doing too much work on its main thread

該 Log 用于提示開發(fā)人員當前存在耗時的任務導致 UI 繪制掉幀超過 30 幀(≈ 16ms * 30 >= 480ms)。

  1. 注意看方法的最后,按照類型順序觸發(fā) doCallbacks 回調(diào)相關任務。
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);

doCallbacks 方法將根據(jù)不同的任務類型依次執(zhí)行其 run 方法:

void doCallbacks(int callbackType, long frameTimeNanos) {
        CallbackRecord callbacks;
        synchronized (mLock) {
            final long now = System.nanoTime();
            // 根據(jù)指定的類型CallbackkQueue中查找到達執(zhí)行時間的CallbackRecord
            callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                    now / TimeUtils.NANOS_PER_MS);
            if (callbacks == null) {
                return;
            }
            mCallbacksRunning = true;

            if (callbackType == Choreographer.CALLBACK_COMMIT) {
                final long jitterNanos = now - frameTimeNanos;
                Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos);
                if (jitterNanos >= 2 * mFrameIntervalNanos) {
                    final long lastFrameOffset = jitterNanos % mFrameIntervalNanos
                            + mFrameIntervalNanos;
                    if (DEBUG_JANK) {
                        Log.d(TAG, "Commit callback delayed by " + (jitterNanos * 0.000001f)
                                + " ms which is more than twice the frame interval of "
                                + (mFrameIntervalNanos * 0.000001f) + " ms!  "
                                + "Setting frame time to " + (lastFrameOffset * 0.000001f)
                                + " ms in the past.");
                        mDebugPrintNextFrameTimeDelta = true;
                    }
                    frameTimeNanos = now - lastFrameOffset;
                    mLastFrameTimeNanos = frameTimeNanos;
                }
            }
        }
        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
            // 迭代執(zhí)行所有任務
            for (CallbackRecord c = callbacks; c != null; c = c.next) {
                // 回調(diào)CallbackRecord的run
                // 其內(nèi)部回調(diào)Callback的run
                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);
        }
}

注意遍歷 CallbackRecord 鏈表調(diào)用其 run 方法:

public void run(long frameTimeNanos) {
            if (token == FRAME_CALLBACK_TOKEN) {
                // 通過postFrameCallback 或 postFrameCallbackDelayed
                // 會執(zhí)行這里
                ((FrameCallback)action).doFrame(frameTimeNanos);
            } else {
                ((Runnable)action).run();
            }
    }

注意 token == FRAME_CALLBACK_TOKEN 表示通過 postFrameCallback 添加的任務。這里就是按照 Callback 類型回調(diào)其 run 方法。

回到 ViewRootImpl 發(fā)起的繪制任務,此時 View 的繪制流程便開始了。

final class TraversalRunnable implements Runnable{

    @Override
    public void run(){
        // View 的繪制任務開始
        doTraversal();
    }
}

至此 Choreographer 的工作流程就已經(jīng)分析清楚了,Choreographer 支持四種類型任務:輸入、動畫、繪制和提交,并配合系統(tǒng)的 VSYNC 進行刷新、繪制等流程。確實做到了統(tǒng)一協(xié)調(diào)管理。

下面,再通過一張圖來加深對 Choreographer 的工作流程的理解。


正如文章開頭介紹 Choreographer 可以配合系統(tǒng)的 VSYNC 信號完成 UI 的繪制任務。那我們便可以通過它來監(jiān)控應用的幀率,雖然 Choreographer 內(nèi)部也實現(xiàn)了對掉幀的監(jiān)控,但是默認只能監(jiān)控超過 30 幀及以上。

不過通過今天的分析,你是否也可以實現(xiàn)一個任意掉幀數(shù)的監(jiān)控呢?并且可以將其用于線上統(tǒng)計,更好的幫助我們優(yōu)化應用的渲染性能。


關于 UI 渲染所涉及的內(nèi)容非常多,文章最后也會附上一些擴展資料,便于更好的學習理解。

文中如有不妥或有更好的分析結果,歡迎您的分享留言或指正。

文章如果對你有幫助,請留個贊吧。


擴展閱讀

其他系列專題

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。