Skip to content

Commit fae07ea

Browse files
committed
Adds support for render detect for Android 6+
1 parent c659d7d commit fae07ea

10 files changed

Lines changed: 168 additions & 211 deletions

File tree

embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/UiLoadExt.kt

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,6 @@ fun createActivityLoadEventEmitter(
4545
*/
4646
fun traceInstanceId(activity: Activity): Int = activity.hashCode()
4747

48-
/**
49-
* Determine if the current instance of the app will fire render events
50-
*
51-
* Disabled temporarily as we figure out how it interacts with Compose navigation
52-
*/
53-
@Suppress("FunctionOnlyReturningConstant", "UNUSED_PARAMETER")
54-
fun hasRenderEvent(versionChecker: VersionChecker): Boolean = false
55-
5648
/**
5749
* Determine if the current instance of the app will pre and post lifecycle events
5850
*/

embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/UiLoadTraceEmitter.kt

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import android.app.Application.ActivityLifecycleCallbacks
44
import io.embrace.android.embracesdk.internal.arch.schema.EmbType
55
import io.embrace.android.embracesdk.internal.spans.PersistableEmbraceSpan
66
import io.embrace.android.embracesdk.internal.spans.SpanService
7+
import io.embrace.android.embracesdk.internal.ui.hasRenderEvent
78
import io.embrace.android.embracesdk.internal.utils.VersionChecker
89
import io.embrace.android.embracesdk.spans.EmbraceSpanEvent
910
import io.embrace.android.embracesdk.spans.ErrorCode
@@ -40,9 +41,9 @@ import java.util.concurrent.atomic.AtomicReference
4041
*
4142
* The end for both [UiLoadType.COLD] and [UiLoadType.HOT]:
4243
*
43-
* - Android 10+, when the Activity's first UI frame finishes rendering and is delivered to the screen
44+
* - Android 6+, when the Activity's first UI frame finishes rendering and is delivered to the screen, as best as we can determine
4445
*
45-
* - Android 9 and lower, when [ActivityLifecycleCallbacks.onActivityResumed] is fired.
46+
* - Android 5, when [ActivityLifecycleCallbacks.onActivityResumed] is fired.
4647
*/
4748
class UiLoadTraceEmitter(
4849
private val spanService: SpanService,
@@ -51,7 +52,7 @@ class UiLoadTraceEmitter(
5152

5253
private val activeTraces: MutableMap<Int, UiLoadTrace> = ConcurrentHashMap()
5354
private var currentInstance: AtomicReference<UiInstance?> = AtomicReference()
54-
private val hasRenderEvent = hasRenderEvent(versionChecker)
55+
private val trackRender = hasRenderEvent(versionChecker)
5556
private val hasPrePostEvents = hasPrePostEvents(versionChecker)
5657

5758
override fun create(instanceId: Int, activityName: String, timestampMs: Long, manualEnd: Boolean) {
@@ -113,7 +114,7 @@ class UiLoadTraceEmitter(
113114
instanceId = instanceId,
114115
timestampMs = timestampMs,
115116
)
116-
} else if (!hasRenderEvent && traceCompleteTrigger(instanceId) == TraceCompleteTrigger.MANUAL) {
117+
} else if (!trackRender && traceCompleteTrigger(instanceId) == TraceCompleteTrigger.MANUAL) {
117118
startChildSpan(
118119
instanceId = instanceId,
119120
timestampMs = timestampMs,
@@ -136,7 +137,7 @@ class UiLoadTraceEmitter(
136137
instanceId = instanceId,
137138
timestampMs = timestampMs,
138139
)
139-
} else if (!hasRenderEvent && endType == TraceCompleteTrigger.MANUAL) {
140+
} else if (!trackRender && endType == TraceCompleteTrigger.MANUAL) {
140141
startChildSpan(
141142
instanceId = instanceId,
142143
timestampMs = timestampMs,
@@ -146,7 +147,7 @@ class UiLoadTraceEmitter(
146147
}
147148

148149
override fun render(instanceId: Int, timestampMs: Long) {
149-
if (hasRenderEvent) {
150+
if (trackRender) {
150151
startChildSpan(
151152
instanceId = instanceId,
152153
timestampMs = timestampMs,
@@ -156,7 +157,7 @@ class UiLoadTraceEmitter(
156157
}
157158

158159
override fun renderEnd(instanceId: Int, timestampMs: Long) {
159-
if (hasRenderEvent) {
160+
if (trackRender) {
160161
endChildSpan(
161162
instanceId = instanceId,
162163
timestampMs = timestampMs,
@@ -267,7 +268,7 @@ class UiLoadTraceEmitter(
267268
private fun determineEndEvent(manualEnd: Boolean): TraceCompleteTrigger {
268269
return if (manualEnd) {
269270
TraceCompleteTrigger.MANUAL
270-
} else if (hasRenderEvent) {
271+
} else if (trackRender) {
271272
TraceCompleteTrigger.RENDER
272273
} else {
273274
TraceCompleteTrigger.RESUME

embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/startup/AppStartupTraceEmitter.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import io.embrace.android.embracesdk.internal.opentelemetry.embStartupActivityNa
1010
import io.embrace.android.embracesdk.internal.session.lifecycle.ProcessStateListener
1111
import io.embrace.android.embracesdk.internal.spans.PersistableEmbraceSpan
1212
import io.embrace.android.embracesdk.internal.spans.SpanService
13+
import io.embrace.android.embracesdk.internal.ui.hasRenderEvent
1314
import io.embrace.android.embracesdk.internal.utils.Provider
1415
import io.embrace.android.embracesdk.internal.utils.VersionChecker
1516
import io.embrace.android.embracesdk.spans.EmbraceSpan
@@ -53,12 +54,12 @@ internal class AppStartupTraceEmitter(
5354
private val processCreatedMs: Long?
5455
private val additionalTrackedIntervals = ConcurrentLinkedQueue<TrackedInterval>()
5556
private val customAttributes: MutableMap<String, String> = ConcurrentHashMap()
56-
private val hasRenderEvent = startupHasRenderEvent(versionChecker)
57+
private val trackRender = hasRenderEvent(versionChecker)
5758
private val appStartupRootSpan = AtomicReference<PersistableEmbraceSpan?>(null)
5859
private val dataCollectionComplete = AtomicBoolean(false)
5960
private val traceEnd = if (manualEnd) {
6061
TraceEnd.READY
61-
} else if (hasRenderEvent) {
62+
} else if (trackRender) {
6263
TraceEnd.RENDERED
6364
} else {
6465
TraceEnd.RESUMED
@@ -277,7 +278,7 @@ internal class AppStartupTraceEmitter(
277278
firstActivityInitMs = firstActivityInitStartMs,
278279
activityInitStartMs = startupActivityPreCreatedMs ?: startupActivityInitStartMs,
279280
activityInitEndMs = startupActivityInitEndMs,
280-
uiLoadedMs = if (hasRenderEvent) firstFrameRenderedMs else startupActivityResumedMs,
281+
uiLoadedMs = if (trackRender) firstFrameRenderedMs else startupActivityResumedMs,
281282
traceEndTimeMs = traceEndTimeMs,
282283
completed = completed,
283284
)
@@ -363,7 +364,7 @@ internal class AppStartupTraceEmitter(
363364
}
364365

365366
if (activityInitEndMs != null && uiLoadedMs != null) {
366-
val uiLoadSpanName = if (hasRenderEvent) {
367+
val uiLoadSpanName = if (trackRender) {
367368
ACTIVITY_RENDER_SPAN
368369
} else {
369370
ACTIVITY_LOAD_SPAN
@@ -443,7 +444,5 @@ internal class AppStartupTraceEmitter(
443444
} else {
444445
null
445446
}
446-
447-
fun startupHasRenderEvent(versionChecker: VersionChecker) = versionChecker.isAtLeast(VERSION_CODES.Q)
448447
}
449448
}

embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/DataCaptureServiceModuleImpl.kt

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,21 @@
11
package io.embrace.android.embracesdk.internal.injection
22

3-
import android.os.Build.VERSION_CODES
43
import io.embrace.android.embracesdk.internal.Systrace
54
import io.embrace.android.embracesdk.internal.capture.activity.UiLoadDataListener
65
import io.embrace.android.embracesdk.internal.capture.activity.UiLoadTraceEmitter
76
import io.embrace.android.embracesdk.internal.capture.activity.createActivityLoadEventEmitter
8-
import io.embrace.android.embracesdk.internal.capture.activity.hasRenderEvent
97
import io.embrace.android.embracesdk.internal.capture.crumbs.ActivityBreadcrumbTracker
108
import io.embrace.android.embracesdk.internal.capture.crumbs.PushNotificationCaptureService
119
import io.embrace.android.embracesdk.internal.capture.startup.AppStartupDataCollector
1210
import io.embrace.android.embracesdk.internal.capture.startup.AppStartupTraceEmitter
13-
import io.embrace.android.embracesdk.internal.capture.startup.AppStartupTraceEmitter.Companion.startupHasRenderEvent
1411
import io.embrace.android.embracesdk.internal.capture.startup.StartupService
1512
import io.embrace.android.embracesdk.internal.capture.startup.StartupServiceImpl
1613
import io.embrace.android.embracesdk.internal.capture.startup.StartupTracker
1714
import io.embrace.android.embracesdk.internal.capture.webview.EmbraceWebViewService
1815
import io.embrace.android.embracesdk.internal.capture.webview.WebViewService
1916
import io.embrace.android.embracesdk.internal.config.ConfigService
2017
import io.embrace.android.embracesdk.internal.session.lifecycle.ActivityLifecycleListener
21-
import io.embrace.android.embracesdk.internal.ui.FirstDrawDetector
18+
import io.embrace.android.embracesdk.internal.ui.createDrawEventEmitter
2219
import io.embrace.android.embracesdk.internal.utils.BuildVersionChecker
2320
import io.embrace.android.embracesdk.internal.utils.VersionChecker
2421

@@ -69,11 +66,7 @@ internal class DataCaptureServiceModuleImpl @JvmOverloads constructor(
6966
StartupTracker(
7067
appStartupDataCollector = appStartupDataCollector,
7168
activityLoadEventEmitter = activityLoadEventEmitter,
72-
drawEventEmitter = if (startupHasRenderEvent(versionChecker)) {
73-
FirstDrawDetector(logger = initModule.logger)
74-
} else {
75-
null
76-
}
69+
drawEventEmitter = createDrawEventEmitter(versionChecker, initModule.logger)
7770
)
7871
}
7972

@@ -93,11 +86,7 @@ internal class DataCaptureServiceModuleImpl @JvmOverloads constructor(
9386
if (uiLoadEventListener != null) {
9487
createActivityLoadEventEmitter(
9588
uiLoadEventListener = uiLoadEventListener,
96-
firstDrawDetector = if (versionChecker.isAtLeast(VERSION_CODES.Q) && hasRenderEvent(versionChecker)) {
97-
FirstDrawDetector(initModule.logger)
98-
} else {
99-
null
100-
},
89+
firstDrawDetector = createDrawEventEmitter(versionChecker, initModule.logger),
10190
autoTraceEnabled = configService.autoDataCaptureBehavior.isUiLoadTracingTraceAll(),
10291
clock = openTelemetryModule.openTelemetryClock,
10392
versionChecker = versionChecker

embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/ui/DrawEventEmitter.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package io.embrace.android.embracesdk.internal.ui
22

33
import android.app.Activity
4+
import android.os.Build
5+
import android.os.Build.VERSION_CODES
6+
import android.os.Looper
7+
import io.embrace.android.embracesdk.internal.logging.EmbLogger
8+
import io.embrace.android.embracesdk.internal.utils.VersionChecker
49

510
/**
611
* Interface that allows callbacks to be registered and invoked when UI draw events happen
@@ -21,3 +26,19 @@ interface DrawEventEmitter {
2126
*/
2227
fun unregisterFirstDrawCallback(activity: Activity)
2328
}
29+
30+
fun createDrawEventEmitter(
31+
versionChecker: VersionChecker,
32+
logger: EmbLogger,
33+
): DrawEventEmitter? = if (supportFrameCommitCallback(versionChecker)) {
34+
FirstDrawDetector(logger)
35+
} else if (hasRenderEvent(versionChecker)) {
36+
HandlerMessageDrawDetector(Looper.getMainLooper())
37+
} else {
38+
null
39+
}
40+
41+
fun hasRenderEvent(versionChecker: VersionChecker) = versionChecker.isAtLeast(VERSION_CODES.M)
42+
43+
private fun supportFrameCommitCallback(versionChecker: VersionChecker) = versionChecker.isAtLeast(VERSION_CODES.Q) &&
44+
(Build.VERSION.SDK_INT != VERSION_CODES.S && Build.VERSION.SDK_INT != VERSION_CODES.S_V2)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package io.embrace.android.embracesdk.internal.ui
2+
3+
import android.app.Activity
4+
import android.os.Build.VERSION_CODES.M
5+
import android.os.Handler
6+
import android.os.Looper
7+
import android.os.Message
8+
import androidx.annotation.RequiresApi
9+
10+
@RequiresApi(M)
11+
class HandlerMessageDrawDetector(
12+
mainLooper: Looper
13+
) : DrawEventEmitter {
14+
15+
private val handler = Handler(mainLooper)
16+
17+
override fun registerFirstDrawCallback(
18+
activity: Activity,
19+
drawBeginCallback: () -> Unit,
20+
firstFrameDeliveredCallback: () -> Unit
21+
) {
22+
val message = Message.obtain(handler, firstFrameDeliveredCallback).apply {
23+
isAsynchronous = true
24+
}
25+
handler.sendMessageAtFrontOfQueue(message)
26+
}
27+
28+
override fun unregisterFirstDrawCallback(activity: Activity) {
29+
}
30+
}

embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/UiLoadExtTest.kt

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import io.embrace.android.embracesdk.internal.ClockTickingActivityLifecycleCallb
1717
import io.embrace.android.embracesdk.internal.ClockTickingActivityLifecycleCallbacks.Companion.PRE_DURATION
1818
import io.embrace.android.embracesdk.internal.ClockTickingActivityLifecycleCallbacks.Companion.STATE_DURATION
1919
import io.embrace.android.embracesdk.internal.session.lifecycle.ActivityLifecycleListener
20+
import io.embrace.android.embracesdk.internal.ui.hasRenderEvent
2021
import io.embrace.android.embracesdk.internal.utils.BuildVersionChecker
2122
import org.junit.Assert.assertEquals
2223
import org.junit.Assert.assertNotNull
@@ -223,21 +224,17 @@ internal class UiLoadExtTest {
223224
stage = "resumeEnd",
224225
timestampMs = START_TIME_MS + (POST_DURATION + STATE_DURATION + PRE_DURATION) * 3
225226
),
226-
// createEvent(
227-
// stage = "render",
228-
// timestampMs = START_TIME_MS + (POST_DURATION + STATE_DURATION + PRE_DURATION) * 3
229-
// ),
230-
// createEvent(
231-
// stage = "renderEnd",
232-
// timestampMs = START_TIME_MS + (POST_DURATION + STATE_DURATION + PRE_DURATION) * 3 + RENDER_DURATION
233-
// ),
234-
// createEvent(
235-
// stage = "discard",
236-
// timestampMs = START_TIME_MS + (POST_DURATION + STATE_DURATION + PRE_DURATION) * 3 + RENDER_DURATION + PRE_DURATION
237-
// ),
227+
createEvent(
228+
stage = "render",
229+
timestampMs = START_TIME_MS + (POST_DURATION + STATE_DURATION + PRE_DURATION) * 3
230+
),
231+
createEvent(
232+
stage = "renderEnd",
233+
timestampMs = START_TIME_MS + (POST_DURATION + STATE_DURATION + PRE_DURATION) * 3 + RENDER_DURATION
234+
),
238235
createEvent(
239236
stage = "discard",
240-
timestampMs = START_TIME_MS + (POST_DURATION + STATE_DURATION + PRE_DURATION) * 3 + PRE_DURATION
237+
timestampMs = START_TIME_MS + (POST_DURATION + STATE_DURATION + PRE_DURATION) * 3 + RENDER_DURATION + PRE_DURATION
241238
),
242239
)
243240

@@ -258,21 +255,17 @@ internal class UiLoadExtTest {
258255
stage = "resumeEnd",
259256
timestampMs = START_TIME_MS + (POST_DURATION + STATE_DURATION + PRE_DURATION) * 2
260257
),
261-
// createEvent(
262-
// stage = "render",
263-
// timestampMs = START_TIME_MS + (POST_DURATION + STATE_DURATION + PRE_DURATION) * 2
264-
// ),
265-
// createEvent(
266-
// stage = "renderEnd",
267-
// timestampMs = START_TIME_MS + (POST_DURATION + STATE_DURATION + PRE_DURATION) * 2 + RENDER_DURATION
268-
// ),
269-
// createEvent(
270-
// stage = "discard",
271-
// timestampMs = START_TIME_MS + (POST_DURATION + STATE_DURATION + PRE_DURATION) * 2 + RENDER_DURATION + PRE_DURATION
272-
// ),
258+
createEvent(
259+
stage = "render",
260+
timestampMs = START_TIME_MS + (POST_DURATION + STATE_DURATION + PRE_DURATION) * 2
261+
),
262+
createEvent(
263+
stage = "renderEnd",
264+
timestampMs = START_TIME_MS + (POST_DURATION + STATE_DURATION + PRE_DURATION) * 2 + RENDER_DURATION
265+
),
273266
createEvent(
274267
stage = "discard",
275-
timestampMs = START_TIME_MS + (POST_DURATION + STATE_DURATION + PRE_DURATION) * 2 + PRE_DURATION
268+
timestampMs = START_TIME_MS + (POST_DURATION + STATE_DURATION + PRE_DURATION) * 2 + RENDER_DURATION + PRE_DURATION
276269
),
277270
)
278271

embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/UiLoadTraceEmitterTest.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import io.embrace.android.embracesdk.internal.clock.nanosToMillis
99
import io.embrace.android.embracesdk.internal.payload.toNewPayload
1010
import io.embrace.android.embracesdk.internal.spans.SpanService
1111
import io.embrace.android.embracesdk.internal.spans.SpanSink
12+
import io.embrace.android.embracesdk.internal.ui.hasRenderEvent
1213
import io.embrace.android.embracesdk.internal.utils.BuildVersionChecker
1314
import io.embrace.android.embracesdk.spans.EmbraceSpanEvent
1415
import io.embrace.android.embracesdk.spans.ErrorCode

0 commit comments

Comments
 (0)