基于 Choreographer 回调 设计卡顿监控
为了设计一个基于 Choreographer 回调的卡顿监控方案,可以通过监听帧的绘制时间来检测可能的卡顿(掉帧)。Choreographer 是 Android 中用于协调屏幕绘制的类,它提供了一个接口,可以在每一帧绘制时调用。下面是一个基于 Choreographer 回调的卡顿监控方案的实现步骤:
-
创建 Choreographer.FrameCallback 实现类:创建一个类,实现 Choreographer.FrameCallback 接口,用于接收每一帧的回调。
-
在每一帧绘制时记录时间戳:在 FrameCallback 的 doFrame() 方法中记录每一帧的时间戳,并计算两帧之间的时间差。如果时间差超过一定阈值(例如 16ms,即60帧每秒),则认为发生了卡顿。
-
记录和处理卡顿信息:当检测到卡顿时,可以记录相关信息,例如发生时间、持续时间等,或者执行相应的处理逻辑,例如上报监控数据。
-
定期更新 FrameCallback:为了持续监控帧率,需要在每次回调中重新安排下一个回调。
示例代码
import android.os.Handler;
import android.os.Looper;
import android.view.Choreographer;
public class FrameRateMonitor {
private long frameIntervalMillis;
private Choreographer choreographer;
private long lastFrameTimeNanos = 0;
private FrameCallback frameCallback;
public FrameRateMonitor() {
choreographer = Choreographer.getInstance();
frameCallback = new FrameCallback();
frameIntervalMillis = getFrameIntervalMillis(getAppContext());
}
private long getFrameIntervalMillis(Activity activity) {
Display display;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
display = activity.getDisplay();
} else {
WindowManager windowManager = (WindowManager) activity.getSystemService(Activity.WINDOW_SERVICE);
display = windowManager.getDefaultDisplay();
}
float refreshRate = display.getRefreshRate();
return (long) (1000 / refreshRate);
}
public void start() {
choreographer.postFrameCallback(frameCallback);
}
public void stop() {
choreographer.removeFrameCallback(frameCallback);
}
private class FrameCallback implements Choreographer.FrameCallback {
@Override
public void doFrame(long frameTimeNanos) {
if (lastFrameTimeNanos != 0) {
long timeDiffNanos = frameTimeNanos - lastFrameTimeNanos;
if (timeDiffNanos > frameIntervalMillis) {
long droppedFrames = timeDiffNanos / frameIntervalMillis;
onFrameDropped(droppedFrames);
}
}
lastFrameTimeNanos = frameTimeNanos;
choreographer.postFrameCallback(this);
}
}
private void onFrameDropped(long droppedFrames) {
// 处理卡顿逻辑,例如记录日志或上报监控数据
System.out.println("Frame dropped: " + droppedFrames);
}
}
关键点说明
Choreographer.getInstance()
获取 Choreographer 实例。FrameCallback
实现Choreographer.FrameCallback
接口。- 在
doFrame
方法中计算两帧之间的时间差,如果超过 16ms(60fps),则认为发生了卡顿。 - 通过
choreographer.postFrameCallback(this)
在每一帧回调中重新安排下一次回调,以确保持续监控。
基于 Looper 自定义 Printer 设计卡顿监控
为了设计一个基于 Looper 自定义 Printer 的卡顿监控方案,可以通过监控主线程消息处理的时间来检测卡顿。Looper 是 Android 中用于管理线程消息队列的类,可以通过自定义 Printer 拦截消息的处理时间,从而监控卡顿情况。
实现步骤
-
自定义 Printer 类:创建一个自定义的 Printer 类,用于拦截和记录消息的处理时间。
-
在主线程中设置自定义 Printer:将自定义的 Printer 设置到主线程的 Looper 中,以便监控主线程消息的处理。
-
记录和处理卡顿信息:在自定义 Printer 中记录每条消息的处理开始和结束时间,并计算处理时间。如果处理时间超过一定阈值(例如 100ms),则认为发生了卡顿。
示例代码
import android.os.Handler;
import android.os.Looper;
import android.os.MessageQueue;
import android.util.Printer;
public class LooperMonitor {
private static final long BLOCK_THRESHOLD_MS = 100; // 卡顿阈值,单位毫秒
private Handler handler = new Handler(Looper.getMainLooper());
private long startTime = 0;
public void start() {
Looper.getMainLooper().setMessageLogging(new Printer() {
@Override
public void println(String x) {
if (x.startsWith(">>>>> Dispatching to")) {
startTime = System.currentTimeMillis();
} else if (x.startsWith("<<<<< Finished to")) {
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
if (duration > BLOCK_THRESHOLD_MS) {
onBlockDetected(duration);
}
}
}
});
}
private void onBlockDetected(long duration) {
// 处理卡顿逻辑,例如记录日志或上报监控数据
System.out.println("UI thread blocked for " + duration + " ms");
}
public void stop() {
Looper.getMainLooper().setMessageLogging(null);
}
}
关键点说明
-
自定义 Printer:在自定义的 Printer 中,通过
println
方法拦截消息处理的开始和结束时间。>>>>> Dispatching to
表示开始处理消息。<<<<< Finished to
表示完成消息处理。
-
记录消息处理时间:在处理开始时记录当前时间,在处理结束时计算处理持续时间。如果持续时间超过预设的卡顿阈值(如 100ms),则认为发生了卡顿。
-
处理卡顿信息:在
onBlockDetected
方法中处理卡顿逻辑,例如记录日志或上报监控数据。 -
启动和停止监控:提供
start
和stop
方法,用于启动和停止卡顿监控。
优化和扩展
- 上报监控数据:可以将卡顿信息上报到服务器,以便进行统计分析。
- 更精细的监控:可以进一步细化监控逻辑,例如记录卡顿发生的具体时间、频率等。
- 结合其他监控手段:可以结合 Choreographer 回调监控帧率,提供更全面的性能监控方案。
基于 FrameMetrics 接口 设计卡顿监控
为了设计一个基于 FrameMetrics
接口的卡顿监控方案,可以利用 FrameMetrics
提供的帧渲染时间来监控帧率,并检测可能的卡顿。FrameMetrics
是 Android 7.0 (API 24) 引入的一个类,它可以捕捉帧的各种性能指标,包括开始时间、结束时间、绘制时间等。
实现步骤
- 注册 FrameMetricsListener:在活动窗口的
Window
上注册FrameMetrics
的监听器,捕捉每一帧的性能数据。 - 监听帧渲染事件:通过
FrameMetrics
获取每一帧的渲染时间,计算是否有卡顿。 - 记录和处理卡顿信息:当检测到卡顿时,记录相关信息或者执行相应的处理逻辑,例如上报监控数据。
示例代码
import android.app.Activity;
import android.os.Build;
import android.util.Log;
import android.view.FrameMetrics;
import android.view.Window;
import android.view.Window.OnFrameMetricsAvailableListener;
public class FrameMetricsMonitor {
private static final long FRAME_TIME_MS = 16; // 60fps, 单位:毫秒
public FrameMetricsMonitor(Activity activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Window window = activity.getWindow();
window.addOnFrameMetricsAvailableListener(new OnFrameMetricsAvailableListener() {
@Override
public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCountSinceLastInvocation) {
long totalDuration = frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION);
long totalDurationMs = totalDuration / 1_000_000; // 纳秒转毫秒
if (totalDurationMs > FRAME_TIME_MS) {
long droppedFrames = totalDurationMs / FRAME_TIME_MS;
onFrameDropped(droppedFrames);
}
}
}, null);
}
}
private void onFrameDropped(long droppedFrames) {
// 处理卡顿逻辑,例如记录日志或上报监控数据
Log.d("FrameMetricsMonitor", "Frame dropped: " + droppedFrames);
}
}
关键点说明
-
注册 FrameMetrics 监听器:
- 在活动的
Window
上注册FrameMetrics
监听器,捕捉每一帧的性能数据。 - 使用
addOnFrameMetricsAvailableListener
方法注册监听器。
- 在活动的
-
监听帧渲染事件:
- 在
onFrameMetricsAvailable
回调方法中,通过FrameMetrics
获取每一帧的总渲染时间 (TOTAL_DURATION
)。 - 将渲染时间从纳秒转换为毫秒,并与设定的帧时间阈值(如 16ms)进行比较。
- 在
-
处理卡顿信息:
- 如果总渲染时间超过设定的帧时间阈值,则认为发生了卡顿,并计算掉帧数量。
- 在
onFrameDropped
方法中处理卡顿逻辑,例如记录日志或上报监控数据。
优化和扩展
- 上报监控数据:可以将卡顿信息上报到服务器,以便进行统计分析。
- 更精细的监控:可以进一步细化监控逻辑,例如记录卡顿发生的具体时间、频率等。
- 结合其他监控手段:可以结合
Looper
和Choreographer
回调监控帧率,提供更全面的性能监控方案。
JankStats 卡顿检测
JankStats 用来追踪和分析应用性能,发现 Jank 卡顿问题,它最低向下兼容到 API 16,可以在绝大多数机器设备上使用,有了它我们不必再求助 BlockCanery 等三方工具了。
implementation "androidx.metrics:metrics-performance:1.0.0-beta01"
我们需要为每个 Window 创建一个 JankStats 实例,并通过 OnFrameListener 回调获取包含是否卡顿在内的帧信息,示例如下:
kotlin 代码解读复制代码class JankLoggingActivity : AppCompatActivity() {
private lateinit var jankStats: JankStats
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ...
// metricsStateHolder可以收集环境信息,跟随帧信息返回
val metricsStateHolder = PerformanceMetricsState.getForHierarchy(binding.root)
// 基于当前 Window 创建 JankStats 实例
jankStats = JankStats.createAndTrack(
window,
Dispatchers.Default.asExecutor(),
jankFrameListener,
)
// 设置 Activity 名字到环境信息
metricsStateHolder.state?.addState("Activity", javaClass.simpleName)
// ...
}
private val jankFrameListener = JankStats.OnFrameListener { frameData ->
// 监听到的帧信息
Log.v("JankStatsSample", frameData.toString())
}
}
PerformanceMetricsState 用来收集你希望跟随 frameData 一起返回的状态信息,比如上面例子中设置了当前 Activity 名称,下面是 frameData 的打印日志:
JankStats.OnFrameListener: FrameData(frameStartNanos=827233150542009, frameDurationUiNanos=27779985, frameDurationCpuNanos=31296985, isJank=false, states=[Activity: JankLoggingActivity])