APP卡頓:Choreographer檢測界面卡頓

前提

本文提供一種卡頓監控的思路,方便在開發過程中及時發現界面卡頓並提示開發者信息。

choreographer用來協調動畫,事件,繪製的時間。

而且Choreographer剛好也提供了一個postFrameCallback
方法供開發者使用。該方法的作用就是在下一幀的時候,會觸發我們向Choreographer註冊的callback回調。

思路

首先講解一下思路,後面再放出代碼。

在界面不卡頓的情況下,我們的界面應該是16.6ms刷新一次,因此每16.6ms會觸發自定義callback

如果發生界面卡頓,那麼自定義callback的觸發間隔時間就會超過16.6ms。

我們再用一個handler來抓取當前函數盞調用方便尋找問題。

每次觸發自定義callback,我們就向handler post一個延時(時間間隔的閾值)任務。
如果下次觸發在16.6ms內,就讓handler移除任務。
如果發生卡頓,而且超過了我們設置的閾值,我們的延時任務就會執行,日誌也就輸出了。

我們就可以從這裏入手,監控界面發生了卡頓。

上代碼

我們的自定義callBack,需要實現Choreographer./FrameCallback/接口,同時需要一個變量用來記錄上次觸發自定義callback的時間,在下一次觸發後,計算時間間隔,超過我們規定的閾值後,就輸出log日誌進行通知。

public class ChoreographerCallBack implements Choreographer.FrameCallback {

    // 一幀的刷新時間間隔
    private final double FRAME_INTERVAL = 16.7;

    // 上次觸發的時間
    private long lastFrameTimeNanos = 0;

    // 用戶輸出日誌的handler
    private Handler mHandler;

    // 輸出日誌的任務
    private Runnable myRun;

    ChoreographerCallBack() {

        mHandler = new Handler(Recorder.getInstance().getHandlerThread().getLooper());

        DumpUtils dumpUtils = new DumpUtils();
        // 輸出日誌的任務
        myRun = dumpUtils.getDumpRunnable();
    }

    @Override
    public void doFrame(long frameTimeNanos) {

        if (lastFrameTimeNanos == 0) {
            // 首次觸發,記錄本次時間
            lastFrameTimeNanos = frameTimeNanos;
        }

        /*
          本來每16.6666ms,都會調用doFrame方法
          先發送消息到隊列中,如果卡頓{@link Constant.BLOCK_DETECT_INTERVAL}這麼久,
          就會打印日誌,否則,就會在下次doFrame方法中移除上次的Runnable。
         */

        if (mHandler.hasCallbacks(myRun)) {
            // 移除上次的Runnable
            mHandler.removeCallbacks(myRun);
        }

        // 發送消息記錄本次幀時間倒計時
        mHandler.postDelayed(myRun, Constant.BLOCK_DETECT_INTERVAL);

        // 發送下一次的callback
        Choreographer.getInstance().postFrameCallback(this);

        // 下一次doframe的時候,需要知道上一次的時間,所以記錄這次的時間
        lastFrameTimeNanos = frameTimeNanos;
    }
}

自定義的輸出日誌任務

public Runnable getDumpRunnable() {

    return new Runnable() {
        @Override
        public void run() {
            StackTraceElement[] stackTrace = Looper.getMainLooper().getThread().getStackTrace();
            StringBuilder sb = new StringBuilder();
            for (StackTraceElement stackTraceElement : stackTrace) {
                sb.append(stackTraceElement.toString());
                sb.append("\n");
            }

            // 記錄日誌,可以用自己的log方式輸出
            Recorder.getInstance().writeBlockMonitorInfo(sb.toString());
        }
    };
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章