SM(Smooth,流畅度)、SF(SkippedFrame,跳帧)
基础数据来源背景: 从Android 4.1(版本代号为Jelly Bean)开始,Android OS开发团队便引入了三个核心元素,即VSYNC、Triple Buffer和Choreographer力图解决严重影响Android口碑问题之一的UI流畅性差的问题。 而其中,Choreographer的主要功能是,当收到VSYNC信号时,在doFrame()里去调用使用者通过postCallback设置的回调函数。目前一共定义了三种类型的回调,它们分别是: CALLBACK_INPUT:优先级最高,和输入事件处理有关。 CALLBACK_ANIMATION:优先级其次,和Animation的处理有关。 CALLBACK_TRAVERSAL:优先级最低,和UI等控件绘制有关。这个函数里面主要是检查当前窗口当前状态,比如说是否依然可见,尺寸,方向,布局是否发生改变(可能是由前面的用户输入触发的),分别调用performMeasure(), performLayout(), performDraw()来完成测量,布局和绘制工作。 由于VSYNC信号每16.6ms产生一次(具体时间依据系统实现),因此理想情况下每秒可执行绘制相关操作60次。但实际情况是,CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL中任意一个节点都有可能因为存在耗时操作而导致doFrame()无法在16.6ms内完成,从而无法在接下来的VSYNC信号到达时进行处理。 因此,SM(Smooth,流畅度)、SF(SkippedFrame,跳帧均选用Choreographer中doFrame()实际执行的次数作为基础数据。
指标计算方法: SF(SkippedFrame,跳帧),即为某个应用程序在单位时间1秒内,跳过执行Choreographer中doFrame()的次数。 SM(Smooth,流畅度),即为某个应用程序在单位时间1秒内,实际执行Choreographer中doFrame()的次数。在获取SF数值的基础上,SM=60-SF。 连续跳帧,与SF的不同在于,它将跳帧按其连续跳帧数进行统计,用以衡量单位时间内的卡顿程度。
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.SystemClock;
import android.view.Choreographer;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
public class MonitorFPS {
private static final long FAKE_FRAME_TIME = 10;
private static final Long MONITOR_INTERVAL = 200L;//设置获取fps的时间为200ms
private static final Long MONITOR_INTERVAL_NANOS = MONITOR_INTERVAL * 1000L * 1000L;
private static final Long MAX_INTERVAL = 1000L;//设置计算fps的单位时间间隔1000ms,即fps/s;
private FPSRecordView mFPSFpsRecordView = null;
private WindowManager mWindowManager = null;
private volatile boolean mFPSState = false;
private IFPSCallBack mIFPSCallBack;
private String mType;
private Choreographer.FrameCallback mFrameCallback;
/**
* @param context
* @param type
*/
public MonitorFPS (Context context, String type) {
mType = type;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
mWindowManager = (WindowManager) context.getSystemService(Activity.WINDOW_SERVICE);
mFPSFpsRecordView = new FPSRecordView(context);
}
}
public interface IFPSCallBack {
void fpsCallBack(double fps);
}
public boolean getMonitorFPSStatus() {
return mFPSState;
}
public void setIFPSCallBack(IFPSCallBack ifpsCallBack) {
mIFPSCallBack = ifpsCallBack;
}
public synchronized void stop() {
if (mFPSState) {
mFPSState = false;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
try {
mWindowManager.removeView(mFPSFpsRecordView);
mFPSFpsRecordView.mStartTime = -1;
mFPSFpsRecordView.mCounter = 0;
} catch (Exception e) {
}
} else if (mFrameCallback != null){
Choreographer.getInstance().removeFrameCallback(mFrameCallback);
}
}
}
public void start() {
if (!mFPSState) {
mFPSState = true;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
startBase();
} else {
startJellyBean();
}
}
}
private void startBase() {
mFPSFpsRecordView.mStartTime = -1;
// UI线程插入空view 计算fps
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
// WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY, 0,
WindowManager.LayoutParams.TYPE_TOAST, 0,
PixelFormat.TRANSLUCENT);
params.gravity = Gravity.LEFT | Gravity.TOP;
params.flags =WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
params.height = 1;
params.width = 1;
try {
mWindowManager.removeView(mFPSFpsRecordView);
} catch (Exception e) {
}
mWindowManager.addView(mFPSFpsRecordView, params);
mFPSFpsRecordView.postDelayed(new Runnable() {
public void run() {
// 停止Fps计算
if (mFPSState) {
mFPSFpsRecordView.invalidate();
mFPSFpsRecordView.postDelayed(this, FAKE_FRAME_TIME);
}
}
}, FAKE_FRAME_TIME);
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void startJellyBean() {
mFrameCallback = new Choreographer.FrameCallback() {
private long mStartTime = -1;
private int mCounter = 0;
@Override
public void doFrame(long frameTimeNanos) {
if (mStartTime == -1) {
mStartTime = frameTimeNanos;
}
long interval = frameTimeNanos - mStartTime;
if (interval > MONITOR_INTERVAL_NANOS) {
double fps = (((double)(mCounter * 1000L * 1000L)) / interval) * MAX_INTERVAL;
if (mIFPSCallBack != null) {
mIFPSCallBack.fpsCallBack(fps);
}
MonitorUtils.monitorOnTimer(mType, "fps", (float)fps);
mFPSState = false;
} else {
++mCounter;
Choreographer.getInstance().postFrameCallback(this);
}
}
};
Choreographer.getInstance().postFrameCallback(mFrameCallback);
}
private class FPSRecordView extends View {
private long mStartTime = -1;
private int mCounter = 0;
public FPSRecordView(Context context) {
super(context);
}
@Override
protected void onDraw(Canvas canvas) {
if (mStartTime == -1) {
mStartTime = SystemClock.elapsedRealtime();
mCounter = 0;
}
long realInterval = SystemClock.elapsedRealtime()-mStartTime;
if (realInterval > MONITOR_INTERVAL) {
double fps = ((double) mCounter/realInterval)*MAX_INTERVAL;
if (mIFPSCallBack != null) {
mIFPSCallBack.fpsCallBack(fps);
}
MonitorUtils.monitorOnTimer(mType,"fps",(float)fps);
MonitorFPS.this.stop();
}
mCounter++;
}
}
}
复制代码