前提
本文提供一种卡顿监控的思路,方便在开发过程中及时发现界面卡顿并提示开发者信息。
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());
}
};
}