学习自
https://juejin.im/post/5a6fd7b86fb9a01ca47ac6e8
adb shell dumpsys gfxinfo
这个是一个方法,但是用的不多
从looper入手
// This must be in a local variable, in case a UI event sets the logger final Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } final long traceTag = me.mTraceTag; if (traceTag != 0) { Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); } try { msg.target.dispatchMessage(msg); } finally { if (traceTag != 0) { Trace.traceEnd(traceTag); } } if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); }可以看到msg的执行前后都有log,所以我们只要拿到前后log的时间差就可以拿到我们msg执行的时间了(视图绘制是通过一个 msg的发送来再handler中得到执行的。
所以只要自定义printer即可
1.setMessageLogging方法来设置我们自定义的printer
2.自定义printer:
重写print方法,如果是dispatch方法前后的那个print,就输出时间戳
- 自定义printer
package com.suning.mobile.ebuy;
import android.os.Looper;
import android.util.Printer;
public class CustomPrinterForGetBlockInfo {
public static void start() {
Looper.getMainLooper().setMessageLogging(new Printer() {
//日志输出有很多种格式,我们这里只捕获ui线程中dispatch上下文的日志信息
//所以这里定义了2个key值,注意不同的手机这2个key值可能不一样,有需要的话这里要做机型适配,
//否则部分手机这里可能抓取不到日志信息
private static final String START = ">>>>> Dispatching";
private static final String END = "<<<<< Finished";
@Override
public void println(String x) {
//这里的思路就是如果发现在打印dispatch方法的 start信息,
//那么我们就在 “时间戳” 之后 post一个runnable
if (x.startsWith(START)) {
LogMonitor.getInstance().startMonitor();
}
//因为我们start 不是立即start runnable 而是在“时间戳” 之后 那么如果在这个时间戳之内
//dispacth方法执行完毕以后的END到来,那么就会remove掉这个runnable
//所以 这里就知道 如果dispatch方法执行时间在时间戳之内 那么我们就认为这个ui没卡顿,不输出任何卡顿信息
//否则就输出卡顿信息 这里卡顿信息主要用StackTraceElement 来输出
if (x.startsWith(END)) {
LogMonitor.getInstance().removeMonitor();
}
}
});
}
}
- 看看我们的LogMoniter
package com.suning.mobile.ebuy;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.util.Log;
public class LogMonitor {
private static LogMonitor sInstance = new LogMonitor();
//HandlerThread 这个其实就是一个thread,只不过相对于普通的thread 他对外暴露了一个looper而已。方便
//我们和handler配合使用
private HandlerThread mLogThread = new HandlerThread("BLOCKINFO");
private Handler mIoHandler;
//这个时间戳的值,通常设置成不超过1000,你可以调低这个数值来优化你的代码。数值越低 暴露的信息就越多
private static final long TIME_BLOCK = 1000L;
private LogMonitor() {
mLogThread.start();
mIoHandler = new Handler(mLogThread.getLooper());
}
private static Runnable mLogRunnable = new Runnable() {
@Override
public void run() {
StringBuilder sb = new StringBuilder();
//把ui线程的block的堆栈信息都打印出来 方便我们定位问题
StackTraceElement[] stackTrace = Looper.getMainLooper().getThread().getStackTrace();
for (StackTraceElement s : stackTrace) {
sb.append(s.toString() + "\n");
}
Log.e("BLOCK", sb.toString());
}
};
public static LogMonitor getInstance() {
return sInstance;
}
public void startMonitor() {
//在time之后 再启动这个runnable 如果在这个time之前调用了removeMonitor 方法,那这个runnable肯定就无法执行了
mIoHandler.postDelayed(mLogRunnable, TIME_BLOCK);
}
public void removeMonitor() {
mIoHandler.removeCallbacks(mLogRunnable);
}
}

统计fps
学习自
https://blog.youkuaiyun.com/mengfeicheng2012/article/details/53768954
在Choreographer中有个回调接口,FrameCallback。
public interface FrameCallback
{
public void doFrame(long frameTimeNanos);
}
doFrame 的注释如下:* Called when a new display frame is being rendered.
就是说,当新的一帧被绘制的时候被调用。
因此我们利用这个特性,可以统计两帧绘制的时间间隔。
主要流程如下:
* 1、实现Choreographer.FrameCallback接口,比如实现类是FrameSkipMonitor
* 2、在doFrame中统计两帧绘制的时间,代码与注释如下:
@Override
public void doFrame(long frameTimeNanos) {
if (mLastFrameNanoTime != 0) {//mLastFrameNanoTime 上一次绘制的时间
long frameInterval = frameTimeNanos - mLastFrameNanoTime;//计算两帧的时间间隔
//如果时间间隔大于最小时间间隔,3*16ms,小于最大的时间间隔,60*16ms,就认为是掉帧,累加统计该时间
//此处解释一下原因: 因为正常情况下,两帧的间隔都是在16ms以内 ,如果我们统计到的两帧间隔时间大于三倍的普通绘制时间,我们就认为是出现了卡顿,之所以设置最大时间间隔,是为了有时候页面不刷新绘制的时候,不做统计处理
if (frameInterval > MIN_FRAME_TIME && frameInterval < MAX_FRAME_TIME) {
long time = 0;
if (mSkipRecordMap.containsKey(mActivityName)) {
time = mSkipRecordMap.get(mActivityName);
}
mSkipRecordMap.put(mActivityName, time + frameInterval);//统计时间
}
}
mLastFrameNanoTime = frameTimeNanos;
Choreographer.getInstance().postFrameCallback(this);
Runtime.getRuntime().maxMemory();
}
3、启动监测
public void start() {
Choreographer.getInstance().postFrameCallback(FrameSkipMonitor.getInstance());
}4、上报处理
在doFrame选中,我们已经统计到了想要的数据,然后我们将time的值除以16600000(因为我们统计的时间是纳秒),就是掉帧的数字。
demo源码地址:https://github.com/FanGuangcheng/FrameSkipMonitor
源码简要解读:
最重要的两个类
FrameSkipMonitor:统计绘制间隔的时间处理操作。
MyActivityLifeCycle:activity相关的跳转纪录操作,通过纪录,就可以统计每个页面的掉帧卡顿时间。
注意点,hasCallBack方法为hide方法,只能通过反射拿到
public boolean isMonitor() {
//网上流传的方法多数是这个,但是这个是错的,因为hasCallbacks 是一个hide函数 你压根调用不了的,只能反射调用
//return mIoHandler.hasCallbacks(mLogRunnable);
try {
//通过详细地类名获取到指定的类
Class<?> handlerClass = Class.forName("android.os.Handler");
//通过方法名,传入参数获取指定方法
java.lang.reflect.Method method = handlerClass.getMethod("hasCallbacks", Runnable.class);
Boolean ret = (Boolean) method.invoke(mIoHandler, mLogRunnable);
return ret;
} catch (Exception e) {
}
return false;
}