帧率和卡顿监控方案
监控应用的帧率 FPS
是评估应用指标和发现卡顿问题的重要指标。常用的监控应用的FPS的方法有:
1. 使用 Choreographer API
使用 Choreographer API
判断掉帧的原理:
- 计算时间间隔:
- 一般每帧之间的时间间隔应该是设备的刷新时间,一般为16ms(60FPS)或 8.3ms(120FPS)或 6.9ms(144FPS)等。
- 监控
doFrame
方法种两次回调的时间差,如果明显高于目标帧时间间隔,说明出现掉帧或者卡顿。
- 设置阈值:
- 设置一个阈值:例如超过20ms就算掉帧,连续多次掉帧就是卡顿。
实现示例:
监控
类
java
import android.os.Build;
import android.os.Looper;
import android.util.Log;
import android.view.Choreographer;
public class FPSMonitor implements Choreographer.FrameCallback {
private static final String TAG = "FrameMonitor";
private static final long FRAME_INTERVAL_NANOS = 16666666; // 16.6ms in nanoseconds (roughly 60 FPS)
private long lastFrameTimeNanos = 0;
private long droppedFramesCount = 0; // 统计掉帧次数
public void start() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
Choreographer.getInstance().postFrameCallback(this);
} else {
Log.e(TAG, "Device does not support Choreographer API");
}
}
@Override
public void doFrame(long frameTimeNanos) {
if (lastFrameTimeNanos == 0) {
lastFrameTimeNanos = frameTimeNanos;
} else {
long elapsedNanos = frameTimeNanos - lastFrameTimeNanos;
if (elapsedNanos > FRAME_INTERVAL_NANOS) {
// 计算掉帧的数量
long droppedFrames = elapsedNanos / FRAME_INTERVAL_NANOS;
droppedFramesCount += droppedFrames;
// 只有在掉帧的次数足够多时才输出日志
if (droppedFramesCount > 5) { // 比如超过 5 帧掉帧才输出日志
Log.w(TAG, "Dropped frames: " + droppedFramesCount);
// 打印堆栈信息,只在严重掉帧时才输出
Thread currentThread = Thread.currentThread();
StackTraceElement[] stackTraceElements = currentThread.getStackTrace();
StringBuilder stackTrace = new StringBuilder("Stack trace:\n");
for (StackTraceElement element : stackTraceElements) {
stackTrace.append("\tat ").append(element.toString()).append("\n");
}
Log.i(TAG, stackTrace.toString());
// 重置掉帧计数器
droppedFramesCount = 0;
}
}
lastFrameTimeNanos = frameTimeNanos;
}
// 继续调度下一帧的回调
Choreographer.getInstance().postFrameCallback(this);
}
}
- 在
Activity
中使用
java
public class MainActivity extends AppCompatActivity {
private FPSMonitor fpsMonitor;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fpsMonitor = new FPSMonitor();
fpsMonitor.start();
}
}
2. 使用 FrameMetrics API 进行掉帧检测
FrameMetrics API
来监控应用的帧率的掉帧情况。FrameMetrics API
提供了一种强大的方法来衡量和分析视图的绘制性能。使用 window 对象注册一个 OnFrameMetricsAvailableListener
, 这个监听器会在每一帧结束时提供 FrameMetrics 数据。
实现示例:
FrameMetrics API 进行掉帧检测并打印相关信息:
java
import android.app.Activity;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.FrameMetrics;
import android.view.Window;
import android.view.Window.OnFrameMetricsAvailableListener;
public class FrameMetricsActivity extends Activity {
private static final String TAG = "FrameMetricsActivity";
private static final int DROPPED_FRAME_LOG_THRESHOLD = 5; // 当掉帧超过5帧时输出日志
private static final long LOG_FRAME_DURATION_THRESHOLD = 20000000; // 超过 20ms 的帧输出警告
private long droppedFramesCount = 0; // 用来统计掉帧数量
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
getWindow().addOnFrameMetricsAvailableListener(new OnFrameMetricsAvailableListener() {
@Override
public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCountSinceLastInvocation) {
long totalDuration = frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION);
long intendedDuration = frameMetrics.getMetric(FrameMetrics.INTENDED_VSYNC_TIMESTAMP)
- frameMetrics.getMetric(FrameMetrics.FRAME_PRESENT_TIMESTAMP);
// 增加掉帧的计数
droppedFramesCount += dropCountSinceLastInvocation;
// 如果掉帧超过阈值才输出日志
if (droppedFramesCount >= DROPPED_FRAME_LOG_THRESHOLD) {
Log.w(TAG, "Dropped frames: " + droppedFramesCount);
// 如果帧的持续时间超过了预期的时间,输出警告
if (totalDuration > intendedDuration + LOG_FRAME_DURATION_THRESHOLD) {
Log.w(TAG, "Frame duration exceeded: " + totalDuration);
}
// 打印堆栈信息,限制频率
logStackTrace();
// 重置掉帧计数
droppedFramesCount = 0;
}
}
}, null);
} else {
Log.e(TAG, "FrameMetrics API not available on this device");
}
}
// 打印当前线程的堆栈信息
private void logStackTrace() {
Thread currentThread = Thread.currentThread();
StackTraceElement[] stackTraceElements = currentThread.getStackTrace();
StringBuilder stackTrace = new StringBuilder("Stack trace:\n");
for (StackTraceElement element : stackTraceElements) {
stackTrace.append("\tat ").append(element.toString()).append("\n");
}
Log.i(TAG, stackTrace.toString());
}
}
TOTAL_DURATION
:表示完整帧的时间,包括应用、合成和发布时间。INTENDED_VSYNC_TIMESTAMP
和FRAME_PRESENT_TIMESTAMP
之间的差值被认为是理想的帧时间。 如果TOTAL_DURATION
大于理想持续时间,则可能存在掉帧。
3. TraceView 和 systrace
4. SurfaceView 的 FPS 监控
如果使用 SurfaceView,可以在绘制回调中统计帧率。
5. OpenGL 的 FPS 监控
在使用 OpenGL 渲染时,在每次 SwapBuffers 时统计帧率。
6. TinyDancer、LeakCanary
性能监控工具也提供了 FPS 监控功能,适合快速应用和调试。