前提
本文提供一種卡頓監控的思路,方便在開發過程中及時發現界面卡頓並提示開發者信息。
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());
}
};
}