blob: 1d9bf39819e19c43971144dbd62b9fad0895e410 [file] [log] [blame]
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.metrics.performance
import android.os.Handler
import android.os.HandlerThread
import android.view.FrameMetrics
import android.view.View
import android.view.Window
import androidx.annotation.RequiresApi
/**
* Subclass of JankStatsBaseImpl records frame timing data for API 24 and later,
* using FrameMetrics (which was introduced in API 24). Jank data is collected by
* setting a [Window.addOnFrameMetricsAvailableListener]
* on the Window associated with the Activity being tracked.
*/
@RequiresApi(24)
internal open class JankStatsApi24Impl(
jankStats: JankStats,
view: View,
private val window: Window
) : JankStatsApi22Impl(jankStats, view) {
// Workaround for situation like b/206956036, where platform would sometimes send completely
// duplicate events through FrameMetrics. When that occurs, simply ignore the latest event
// that has the exact same start time.
var prevStart = 0L
// Used to avoid problems with data gathered before things are set up
var listenerAddedTime: Long = 0
private val frameMetricsAvailableListenerDelegate: Window.OnFrameMetricsAvailableListener =
Window.OnFrameMetricsAvailableListener { _, frameMetrics, _ ->
val startTime = getFrameStartTime(frameMetrics)
// ignore historical data gathered before we started listening
if (startTime >= listenerAddedTime && startTime != prevStart) {
val expectedDuration = getExpectedFrameDuration(frameMetrics) *
jankStats.jankHeuristicMultiplier
jankStats.logFrameData(
startTime,
getFrameDuration(frameMetrics),
expectedDuration.toLong()
)
prevStart = startTime
}
}
internal open fun getFrameDuration(frameMetrics: FrameMetrics): Long {
return frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION)
}
internal open fun getFrameStartTime(frameMetrics: FrameMetrics): Long {
return getFrameStartTime()
}
open fun getExpectedFrameDuration(metrics: FrameMetrics): Long {
return getExpectedFrameDuration(decorViewRef.get())
}
override fun setupFrameTimer(enable: Boolean) {
window.let {
if (enable) {
if (listenerAddedTime == 0L) {
val delegates = window.getOrCreateFrameMetricsListenerDelegates()
delegates.add(frameMetricsAvailableListenerDelegate)
listenerAddedTime = System.nanoTime()
}
} else {
window.removeFrameMetricsListenerDelegate(frameMetricsAvailableListenerDelegate)
listenerAddedTime = 0
}
}
}
companion object {
// Need a Handler for FrameMetricsListener; just use a singleton, no need for Thread
// overhead per JankStats instance
internal var frameMetricsHandler: Handler? = null
}
@RequiresApi(24)
private fun Window.removeFrameMetricsListenerDelegate(
delegate: Window.OnFrameMetricsAvailableListener
) {
val delegator = decorView.getTag(R.id.metricsDelegator) as
DelegatingFrameMetricsListener?
with(delegator?.delegates) {
this?.remove(delegate)
if (this?.size == 0) {
removeOnFrameMetricsAvailableListener(delegator)
decorView.setTag(R.id.metricsDelegator, null)
}
}
}
/**
* This function returns the current list of FrameMetricsListener delegates.
* If no such list exists, it will create it, and add a root listener which
* delegates to that list.
*/
@RequiresApi(24)
private fun Window.getOrCreateFrameMetricsListenerDelegates():
MutableList<Window.OnFrameMetricsAvailableListener> {
var delegator = decorView.getTag(R.id.metricsDelegator) as
DelegatingFrameMetricsListener?
if (delegator == null) {
var delegates = mutableListOf<Window.OnFrameMetricsAvailableListener>()
delegator = DelegatingFrameMetricsListener(delegates)
// First listener for this window; create the delegates list and
// add a listener to the window
if (JankStatsApi24Impl.frameMetricsHandler == null) {
val thread = HandlerThread("FrameMetricsAggregator")
thread.start()
JankStatsApi24Impl.frameMetricsHandler = Handler(thread.looper)
}
addOnFrameMetricsAvailableListener(delegator,
JankStatsApi24Impl.frameMetricsHandler
)
decorView.setTag(R.id.metricsDelegator, delegator)
}
return delegator.delegates
}
}
/**
* To avoid having multiple frame metrics listeners for a given window (if the client
* creates multiple JankStats instances on that window), we use a single listener and
* delegate out to the multiple listeners provided by the client. This single instance
* and the list of delegates are cached in view tags in the DecorView for the window.
*/
@RequiresApi(24)
private class DelegatingFrameMetricsListener(
val delegates: MutableList<Window.OnFrameMetricsAvailableListener>
) : Window.OnFrameMetricsAvailableListener {
override fun onFrameMetricsAvailable(
window: Window?,
frameMetrics: FrameMetrics?,
dropCount: Int
) {
for (delegate in delegates) {
delegate.onFrameMetricsAvailable(window, frameMetrics, dropCount)
}
// Remove singleFrame states now that we are done processing this frame
if (window?.decorView != null) {
val holder = PerformanceMetricsState.getForHierarchy(window.decorView)
holder.state?.cleanupSingleFrameStates()
}
}
}