Matrix
在最新版本的Matrix上,已经放弃以前的UIThreadMonitor,改用com.tencent.matrix.trace.tracer.FrameTracer来监听帧率
OnFrameMetricsAvailableListener
在 Android 7.0, 增加了一个 Api 可以方便的统计一个 Window 的 View 树的绘制耗时。
FrameMetrics 指标 | 说明 |
---|---|
ANIMATION_DURATION | 表示执行动画回调的耗时 |
COMMAND_ISSUE_DURATION | 表示向 GPU 发出绘制命令的耗时 |
DRAW_DURATION | 表示将 View 树转换为 DisplayList 的耗时 |
FIRST_DRAW_FRAME | 表示当前帧是否是当前 Window 布局中绘制的第一帧 |
INPUT_HANDLING_DURATION | 表示处理输入事件回调的耗时 |
INTENDED_VSYNC_TIMESTAMP | 当前帧的预期开始时间, 如果此值与 VSYNC_TIMESTAMP 不同,则表示 UI 线程上发生了阻塞,阻止了 UI 线程及时响应vsync信号 |
LAYOUT_MEASURE_DURATION | 表示对 View 树进行 measure 和 layout 所花的时间 |
SWAP_BUFFERS_DURATION | 表示将此帧的帧缓冲区发送给显示子系统所花的时间 |
SYNC_DURATION | 表示将 DisplayList 与渲染线程同步所花的时间 |
TOTAL_DURATION | 表示此帧渲染并发布给显示子系统所花费的总时间, 等于所有其他具有时间价值的指标的值之和 |
UNKNOWN_DELAY_DURATION | 表示等待 UI 线程响应并处理帧所经过的时间, 大多数情况下应为0 |
VSYNC_TIMESTAMP | 在所有 vsync 监听器和帧绘制中使用的时间值(Choreographer 的帧回调, 动画, View#getDrawingTime 等) |
以上所有单位为纳秒,以上所有指标都存储在一个 long
类型的数组中, 使用 FrameMetrics#getMetric
方法可以从中提取。
使用
@RequiresApi(Build.VERSION_CODES.O)
private fun initFrameMetricsListener() {
val thread = HandlerThread("frame-stat").apply { start() }
val handler = Handler(thread.looper)
window.addOnFrameMetricsAvailableListener(object : Window.OnFrameMetricsAvailableListener {
override fun onFrameMetricsAvailable(
window: Window?, metric: FrameMetrics?, dropCountSinceLastInvocation: Int
) {
// 会在 handler 对应的 thread 中执行
val frameMetricsCopy = FrameMetrics(metric) /* 注意需要做深拷贝, 再使用 */
val vsyncTimestamp = frameMetricsCopy.getMetric(FrameMetrics.VSYNC_TIMESTAMP)
val intendedVsyncTimestamp =
frameMetricsCopy.getMetric(FrameMetrics.INTENDED_VSYNC_TIMESTAMP)
val totalDuration: Long = frameMetricsCopy.getMetric(FrameMetrics.TOTAL_DURATION)
Log.d("FrameStat","is first frame: ${frameMetricsCopy.getMetric(FrameMetrics.FIRST_DRAW_FRAME) == 1L}")
Log.d("FrameStat","measure layout: ${frameMetricsCopy.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION) / 1000000} ms")
Log.d("FrameStat","draw: ${frameMetricsCopy.getMetric(FrameMetrics.DRAW_DURATION) / 1000000} ms")
Log.d("FrameStat", "total: ${totalDuration / 1000000} ms")
Log.d("FrameStat", "delay draw: ${vsyncTimestamp != intendedVsyncTimestamp}")
//获取屏幕刷新率
updateRefreshRate(window)
//获取每一帧耗时
val frameIntervalNanos: Float = TIME_SECOND_TO_NANO / cachedRefreshRate
//计算本帧一共掉了多少帧
val droppedFrames =
Math.max(0f, (totalDuration - frameIntervalNanos) / frameIntervalNanos)
if (droppedFrames > 0) {
Log.d("FrameStat", "droppedFrames = $droppedFrames")
}
Log.d("FrameStat", "=============")
}
private val TIME_SECOND_TO_NANO = 1000000000
private var cachedRefreshRate: Float = 60f
private var lastModeId = -1
private var attributes: WindowManager.LayoutParams? = null
//更新屏幕刷新率
private fun updateRefreshRate(window: Window?) {
if (attributes == null) {
attributes = window?.attributes
}
attributes?.let {
if (it.preferredDisplayModeId != lastModeId) {
lastModeId = it.preferredDisplayModeId
cachedRefreshRate = getRefreshRate(window)
}
}
}
private fun getRefreshRate(window: Window?): Float {
if (window == null) {
return cachedRefreshRate
}
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.context.display!!.refreshRate
} else {
window.windowManager.defaultDisplay.refreshRate
}
}
}, handler)
}
一些注意点
1、dropCountSinceLastInvocation:方法注释里面说这个值为从上一次回调开始掉帧数,不过一直为0
2、虽然注释说INTENDED_VSYNC_TIMESTAMP与VSYNC_TIMESTAMP不一致,则代表ui线程出现阻塞,但是在Touch里面耗时,这个为一致的,推荐使用TOTAL_DURATION来计算帧率
FrameMetricsAggregator
如果我们使用addOnFrameMetricsAvailableListener,但是其实我们可以使用官方提供的FrameMetricsAggregator
private val frameMetricsAggregator = FrameMetricsAggregator()
override fun onResume() {
frameMetricsAggregator.add(this)
super.onResume()
}
override fun onPause() {
Log.i("FrameStat","onPause: ${FrameMetricsCalculator.calculateFrameMetrics(frameMetricsAggregator.metrics)}")
frameMetricsAggregator.remove(this)
frameMetricsAggregator.reset()
super.onPause()
}
override fun onDestroy() {
frameMetricsAggregator.stop()
super.onDestroy()
}
FrameMetricsAggregator内部存储比较有意思,是有一个SparseIntArray数组SparseIntArray[] mMetrics = new SparseIntArray[LAST_INDEX + 1],存储各个阶段的耗时SparseIntArray的key为耗时,value为该耗时的个数。
mMetrics[TOTAL_INDEX]:
{3=8, 4=13, 5=2, 6=44, 7=4, 15=1, 196=1, 198=1, 204=1, 209=1, 210=1, 233=1, 265=1}
计算帧率辅助类
object FrameMetricsCalculator {
fun calculateFrameMetrics(arr: Array<SparseIntArray?>?): PerfFrameMetrics {
var totalFrames = 0
var slowFrames = 0
var frozenFrames = 0
if (arr != null) {
val frameTimes = arr[FrameMetricsAggregator.TOTAL_INDEX]
if (frameTimes != null) {
for (i in 0 until frameTimes.size()) {
val frameTime = frameTimes.keyAt(i)
val numFrames = frameTimes.valueAt(i)
totalFrames += numFrames
if (frameTime > 700) {
frozenFrames += numFrames
}
if (frameTime > 16) {
slowFrames += numFrames
}
}
}
}
return PerfFrameMetrics(totalFrames, slowFrames, frozenFrames)
}
data class PerfFrameMetrics(
val totalFrames: Int,
val slowFrames: Int,
val frozenFrames: Int
) {
fun deltaFrameMetricsFromSnapshot(that: PerfFrameMetrics): PerfFrameMetrics {
val newTotalFrames = totalFrames - that.totalFrames
val newSlowFrames = slowFrames - that.slowFrames
val newFrozenFrames = frozenFrames - that.frozenFrames
return PerfFrameMetrics(newTotalFrames, newSlowFrames, newFrozenFrames)
}
}
}
监听主线程耗时
使用Looper#Printer也可以监听耗时,如下所示
private fun initLooperListener() {
Looper.getMainLooper().setMessageLogging(object : Printer {
private var isStart = false
private var startNs = 0L
override fun println(x: String?) {
x?.let {
if (it[0] == '>') {
isStart = true
startNs = System.nanoTime()
} else {
if (isStart) {
Log.i("FrameStat", "const = ${(System.nanoTime() - startNs) / 1000000f}")
}
}
}
}
})
}