Skip to content

帧率和卡顿监控方案

监控应用的帧率 FPS 是评估应用指标和发现卡顿问题的重要指标。常用的监控应用的FPS的方法有:

1. 使用 Choreographer API

使用 Choreographer API 判断掉帧的原理:

  1. 计算时间间隔
    1. 一般每帧之间的时间间隔应该是设备的刷新时间,一般为16ms(60FPS)或 8.3ms(120FPS)或 6.9ms(144FPS)等。
    2. 监控 doFrame 方法种两次回调的时间差,如果明显高于目标帧时间间隔,说明出现掉帧或者卡顿。
  2. 设置阈值
    1. 设置一个阈值:例如超过20ms就算掉帧,连续多次掉帧就是卡顿。

实现示例:

  1. 监控
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);
    }
}
  1. 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 监控功能,适合快速应用和调试。

Released under the MIT License.