@@ -24,18 +24,17 @@ import androidx.metrics.performance.FrameData
24
24
import androidx.metrics.performance.JankStats
25
25
import com.datadog.android.api.InternalLogger
26
26
import com.datadog.android.core.internal.system.BuildSdkVersionProvider
27
+ import com.datadog.android.rum.internal.domain.FrameMetricsData
27
28
import java.lang.ref.WeakReference
28
29
import java.util.WeakHashMap
29
- import java.util.concurrent.TimeUnit
30
30
31
31
/* *
32
32
* Utility class listening to frame rate information.
33
33
*/
34
34
internal class JankStatsActivityLifecycleListener (
35
- private val vitalObserver : VitalObserver ,
35
+ private val delegates : List < FrameStateListener > ,
36
36
private val internalLogger : InternalLogger ,
37
37
private val jankStatsProvider : JankStatsProvider = JankStatsProvider .DEFAULT ,
38
- private var screenRefreshRate : Double = 60.0 ,
39
38
private var buildSdkVersionProvider : BuildSdkVersionProvider = BuildSdkVersionProvider .DEFAULT
40
39
) : ActivityLifecycleCallbacks, JankStats.OnFrameListener {
41
40
@@ -44,7 +43,8 @@ internal class JankStatsActivityLifecycleListener(
44
43
internal val activeActivities = WeakHashMap <Window , MutableList <WeakReference <Activity >>>()
45
44
internal var display: Display ? = null
46
45
private var frameMetricsListener: DDFrameMetricsListener ? = null
47
- internal var frameDeadline = SIXTEEN_MS_NS
46
+
47
+ private val frameMetricsData = FrameMetricsData ()
48
48
49
49
// region ActivityLifecycleCallbacks
50
50
@MainThread
@@ -143,7 +143,7 @@ internal class JankStatsActivityLifecycleListener(
143
143
if (activeActivities[activity.window].isNullOrEmpty()) {
144
144
activeWindowsListener.remove(activity.window)
145
145
activeActivities.remove(activity.window)
146
- if (buildSdkVersionProvider.version >= Build .VERSION_CODES .S ) {
146
+ if (buildSdkVersionProvider.version >= Build .VERSION_CODES .N ) {
147
147
unregisterMetricListener(activity.window)
148
148
}
149
149
}
@@ -154,23 +154,8 @@ internal class JankStatsActivityLifecycleListener(
154
154
// region JankStats.OnFrameListener
155
155
156
156
override fun onFrame (volatileFrameData : FrameData ) {
157
- val durationNs = volatileFrameData.frameDurationUiNanos
158
- if (durationNs > 0.0 ) {
159
- var frameRate = (ONE_SECOND_NS / durationNs)
160
-
161
- if (buildSdkVersionProvider.version >= Build .VERSION_CODES .S ) {
162
- screenRefreshRate = ONE_SECOND_NS / frameDeadline
163
- } else if (buildSdkVersionProvider.version == Build .VERSION_CODES .R ) {
164
- screenRefreshRate = display?.refreshRate?.toDouble() ? : SIXTY_FPS
165
- }
166
-
167
- // If normalized frame rate is still at over 60fps it means the frame rendered
168
- // quickly enough for the devices refresh rate.
169
- frameRate = (frameRate * (SIXTY_FPS / screenRefreshRate)).coerceAtMost(MAX_FPS )
170
-
171
- if (frameRate > MIN_FPS ) {
172
- vitalObserver.onNewSample(frameRate)
173
- }
157
+ for (i in delegates.indices) {
158
+ delegates[i].onFrame(volatileFrameData)
174
159
}
175
160
}
176
161
@@ -216,7 +201,7 @@ internal class JankStatsActivityLifecycleListener(
216
201
@SuppressLint(" NewApi" )
217
202
@MainThread
218
203
private fun trackWindowMetrics (isKnownWindow : Boolean , window : Window , activity : Activity ) {
219
- if (buildSdkVersionProvider.version >= Build .VERSION_CODES .S && ! isKnownWindow) {
204
+ if (buildSdkVersionProvider.version >= Build .VERSION_CODES .N && ! isKnownWindow) {
220
205
registerMetricListener(window)
221
206
} else if (display == null && buildSdkVersionProvider.version == Build .VERSION_CODES .R ) {
222
207
// Fallback - Android 30 allows apps to not run at a fixed 60hz, but didn't yet have
@@ -226,14 +211,37 @@ internal class JankStatsActivityLifecycleListener(
226
211
}
227
212
}
228
213
229
- @RequiresApi(Build .VERSION_CODES .S )
214
+ @RequiresApi(Build .VERSION_CODES .N )
230
215
private fun registerMetricListener (window : Window ) {
231
216
if (frameMetricsListener == null ) {
232
217
frameMetricsListener = DDFrameMetricsListener ()
233
218
}
219
+ // TODO RUM-8799: handler thread can be used instead
234
220
val handler = Handler (Looper .getMainLooper())
235
- // Only hardware accelerated views can be tracked with metrics listener
236
- if (window.peekDecorView()?.isHardwareAccelerated == true ) {
221
+ val decorView = window.peekDecorView()
222
+
223
+ if (decorView == null ) {
224
+ internalLogger.log(
225
+ InternalLogger .Level .WARN ,
226
+ InternalLogger .Target .MAINTAINER ,
227
+ { " Unable to attach JankStatsListener to window, decorView is null" }
228
+ )
229
+ return
230
+ }
231
+
232
+ // We need to postpone this operation because isHardwareAccelerated will return
233
+ // false until the view is attached to the window. Note that in this case main looper should be used
234
+ decorView.post {
235
+ // Only hardware accelerated views can be tracked with metrics listener
236
+ if (! decorView.isHardwareAccelerated) {
237
+ internalLogger.log(
238
+ InternalLogger .Level .WARN ,
239
+ InternalLogger .Target .MAINTAINER ,
240
+ { " Unable to attach JankStatsListener to window, decorView is not hardware accelerated" }
241
+ )
242
+ return @post
243
+ }
244
+
237
245
frameMetricsListener?.let { listener ->
238
246
try {
239
247
@Suppress(" UnsafeThirdPartyFunctionCall" ) // Listener can't be null here
@@ -247,12 +255,6 @@ internal class JankStatsActivityLifecycleListener(
247
255
)
248
256
}
249
257
}
250
- } else {
251
- internalLogger.log(
252
- InternalLogger .Level .WARN ,
253
- InternalLogger .Target .MAINTAINER ,
254
- { " Unable to attach JankStatsListener to window, decorView is null or not hardware accelerated" }
255
- )
256
258
}
257
259
}
258
260
@@ -273,13 +275,42 @@ internal class JankStatsActivityLifecycleListener(
273
275
@RequiresApi(Build .VERSION_CODES .N )
274
276
inner class DDFrameMetricsListener : Window .OnFrameMetricsAvailableListener {
275
277
276
- @RequiresApi(Build .VERSION_CODES .S )
278
+ @RequiresApi(Build .VERSION_CODES .N )
277
279
override fun onFrameMetricsAvailable (
278
280
window : Window ,
279
281
frameMetrics : FrameMetrics ,
280
282
dropCountSinceLastInvocation : Int
281
283
) {
282
- frameDeadline = frameMetrics.getMetric(FrameMetrics .DEADLINE )
284
+ for (i in delegates.indices) {
285
+ delegates[i].onFrameMetricsData(frameMetricsData.update(frameMetrics))
286
+ }
287
+ }
288
+ }
289
+
290
+ @RequiresApi(Build .VERSION_CODES .N )
291
+ private fun FrameMetricsData.update (frameMetrics : FrameMetrics ) = apply {
292
+ displayRefreshRate = display?.refreshRate?.toDouble() ? : SIXTY_FPS
293
+ if (buildSdkVersionProvider.version >= Build .VERSION_CODES .N ) {
294
+ unknownDelayDuration = frameMetrics.getMetric(FrameMetrics .UNKNOWN_DELAY_DURATION )
295
+ inputHandlingDuration = frameMetrics.getMetric(FrameMetrics .INPUT_HANDLING_DURATION )
296
+ animationDuration = frameMetrics.getMetric(FrameMetrics .ANIMATION_DURATION )
297
+ layoutMeasureDuration = frameMetrics.getMetric(FrameMetrics .LAYOUT_MEASURE_DURATION )
298
+ drawDuration = frameMetrics.getMetric(FrameMetrics .DRAW_DURATION )
299
+ syncDuration = frameMetrics.getMetric(FrameMetrics .SYNC_DURATION )
300
+ commandIssueDuration = frameMetrics.getMetric(FrameMetrics .COMMAND_ISSUE_DURATION )
301
+ swapBuffersDuration = frameMetrics.getMetric(FrameMetrics .SWAP_BUFFERS_DURATION )
302
+ totalDuration = frameMetrics.getMetric(FrameMetrics .TOTAL_DURATION )
303
+ firstDrawFrame = frameMetrics.getMetric(FrameMetrics .FIRST_DRAW_FRAME ) == IS_FIRST_DRAW_FRAME
304
+ }
305
+ @SuppressLint(" InlinedApi" )
306
+ if (buildSdkVersionProvider.version >= Build .VERSION_CODES .O ) {
307
+ intendedVsyncTimestamp = frameMetrics.getMetric(FrameMetrics .INTENDED_VSYNC_TIMESTAMP )
308
+ vsyncTimestamp = frameMetrics.getMetric(FrameMetrics .VSYNC_TIMESTAMP )
309
+ }
310
+ @SuppressLint(" InlinedApi" )
311
+ if (buildSdkVersionProvider.version >= Build .VERSION_CODES .S ) {
312
+ gpuDuration = frameMetrics.getMetric(FrameMetrics .GPU_DURATION )
313
+ deadline = frameMetrics.getMetric(FrameMetrics .DEADLINE )
283
314
}
284
315
}
285
316
@@ -292,12 +323,7 @@ internal class JankStatsActivityLifecycleListener(
292
323
" shouldn't happen."
293
324
internal const val JANK_STATS_TRACKING_DISABLE_ERROR =
294
325
" Failed to disable JankStats tracking"
295
-
296
- private val ONE_SECOND_NS : Double = TimeUnit .SECONDS .toNanos(1 ).toDouble()
297
-
298
- private const val MIN_FPS : Double = 1.0
299
- private const val MAX_FPS : Double = 60.0
300
326
private const val SIXTY_FPS : Double = 60.0
301
- private const val SIXTEEN_MS_NS : Long = 16666666
327
+ private const val IS_FIRST_DRAW_FRAME = 1L
302
328
}
303
329
}
0 commit comments