Skip to content

Commit c3e59ce

Browse files
committed
RUM-16664: Update RumFeature to use new shared factories and interfaces
1 parent f1dfbd9 commit c3e59ce

4 files changed

Lines changed: 50 additions & 69 deletions

File tree

features/dd-sdk-android-rum/api/apiSurface

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -343,10 +343,11 @@ abstract class com.datadog.android.rum.tracking.ActivityLifecycleTrackingStrateg
343343
override fun onActivityCreated(android.app.Activity, android.os.Bundle?)
344344
override fun onActivityResumed(android.app.Activity)
345345
protected fun <T> withSdkCore((com.datadog.android.api.feature.FeatureSdkCore) -> T): T?
346-
class com.datadog.android.rum.tracking.ActivityViewTrackingStrategy : ActivityLifecycleTrackingStrategy, ViewTrackingStrategy
346+
class com.datadog.android.rum.tracking.ActivityViewTrackingStrategy : ActivityLifecycleTrackingStrategy, ViewTrackingStrategy, com.datadog.android.rum.internal.tracking.ReplayableViewTrackingStrategy
347347
constructor(Boolean, ComponentPredicate<android.app.Activity> = AcceptAllActivities())
348348
override fun onActivityResumed(android.app.Activity)
349349
override fun onActivityStopped(android.app.Activity)
350+
override fun onLateActivityReady(android.app.Activity)
350351
override fun equals(Any?): Boolean
351352
override fun hashCode(): Int
352353
fun android.os.Bundle?.convertToRumViewAttributes(): Map<String, Any?>
@@ -367,11 +368,12 @@ class com.datadog.android.rum.tracking.MixedViewTrackingStrategy : ActivityLifec
367368
override fun unregister(android.content.Context?)
368369
override fun equals(Any?): Boolean
369370
override fun hashCode(): Int
370-
class com.datadog.android.rum.tracking.NavigationViewTrackingStrategy : ActivityLifecycleTrackingStrategy, ViewTrackingStrategy, androidx.navigation.NavController.OnDestinationChangedListener
371+
class com.datadog.android.rum.tracking.NavigationViewTrackingStrategy : ActivityLifecycleTrackingStrategy, ViewTrackingStrategy, androidx.navigation.NavController.OnDestinationChangedListener, com.datadog.android.rum.internal.tracking.ReplayableViewTrackingStrategy
371372
constructor(Int, Boolean, ComponentPredicate<androidx.navigation.NavDestination> = AcceptAllNavDestinations())
372373
override fun onActivityStarted(android.app.Activity)
373374
override fun onActivityStopped(android.app.Activity)
374375
override fun onActivityPaused(android.app.Activity)
376+
override fun onLateActivityReady(android.app.Activity)
375377
override fun onDestinationChanged(androidx.navigation.NavController, androidx.navigation.NavDestination, android.os.Bundle?)
376378
fun startTracking()
377379
fun stopTracking()

features/dd-sdk-android-rum/api/dd-sdk-android-rum.api

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8293,14 +8293,15 @@ public abstract class com/datadog/android/rum/tracking/ActivityLifecycleTracking
82938293
protected final fun withSdkCore (Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
82948294
}
82958295

8296-
public final class com/datadog/android/rum/tracking/ActivityViewTrackingStrategy : com/datadog/android/rum/tracking/ActivityLifecycleTrackingStrategy, com/datadog/android/rum/tracking/ViewTrackingStrategy {
8296+
public final class com/datadog/android/rum/tracking/ActivityViewTrackingStrategy : com/datadog/android/rum/tracking/ActivityLifecycleTrackingStrategy, com/datadog/android/rum/internal/tracking/ReplayableViewTrackingStrategy, com/datadog/android/rum/tracking/ViewTrackingStrategy {
82978297
public fun <init> (Z)V
82988298
public fun <init> (ZLcom/datadog/android/rum/tracking/ComponentPredicate;)V
82998299
public synthetic fun <init> (ZLcom/datadog/android/rum/tracking/ComponentPredicate;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
83008300
public fun equals (Ljava/lang/Object;)Z
83018301
public fun hashCode ()I
83028302
public fun onActivityResumed (Landroid/app/Activity;)V
83038303
public fun onActivityStopped (Landroid/app/Activity;)V
8304+
public fun onLateActivityReady (Landroid/app/Activity;)V
83048305
}
83058306

83068307
public final class com/datadog/android/rum/tracking/BundleExtKt {
@@ -8339,13 +8340,14 @@ public final class com/datadog/android/rum/tracking/MixedViewTrackingStrategy :
83398340
public fun unregister (Landroid/content/Context;)V
83408341
}
83418342

8342-
public final class com/datadog/android/rum/tracking/NavigationViewTrackingStrategy : com/datadog/android/rum/tracking/ActivityLifecycleTrackingStrategy, androidx/navigation/NavController$OnDestinationChangedListener, com/datadog/android/rum/tracking/ViewTrackingStrategy {
8343+
public final class com/datadog/android/rum/tracking/NavigationViewTrackingStrategy : com/datadog/android/rum/tracking/ActivityLifecycleTrackingStrategy, androidx/navigation/NavController$OnDestinationChangedListener, com/datadog/android/rum/internal/tracking/ReplayableViewTrackingStrategy, com/datadog/android/rum/tracking/ViewTrackingStrategy {
83438344
public fun <init> (IZLcom/datadog/android/rum/tracking/ComponentPredicate;)V
83448345
public synthetic fun <init> (IZLcom/datadog/android/rum/tracking/ComponentPredicate;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
83458346
public fun onActivityPaused (Landroid/app/Activity;)V
83468347
public fun onActivityStarted (Landroid/app/Activity;)V
83478348
public fun onActivityStopped (Landroid/app/Activity;)V
83488349
public fun onDestinationChanged (Landroidx/navigation/NavController;Landroidx/navigation/NavDestination;Landroid/os/Bundle;)V
8350+
public fun onLateActivityReady (Landroid/app/Activity;)V
83498351
public final fun startTracking ()V
83508352
public final fun stopTracking ()V
83518353
}

features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt

Lines changed: 40 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ import com.datadog.android.rum.internal.startup.RumStartupScenario
9494
import com.datadog.android.rum.internal.startup.RumTTIDInfo
9595
import com.datadog.android.rum.internal.thread.NoOpScheduledExecutorService
9696
import com.datadog.android.rum.internal.tracking.JetpackViewAttributesProvider
97+
import com.datadog.android.rum.internal.tracking.ReplayableViewTrackingStrategy
9798
import com.datadog.android.rum.internal.tracking.NoOpInteractionPredicate
9899
import com.datadog.android.rum.internal.tracking.NoOpUserActionTrackingStrategy
99100
import com.datadog.android.rum.internal.tracking.UserActionTrackingStrategy
@@ -137,12 +138,12 @@ import com.datadog.android.rum.tracking.ViewAttributesProvider
137138
import com.datadog.android.rum.tracking.ViewTrackingStrategy
138139
import com.datadog.android.telemetry.model.TelemetryConfigurationEvent
139140
import java.util.Locale
141+
import java.util.WeakHashMap
140142
import java.util.concurrent.ExecutorService
141143
import java.util.concurrent.ScheduledExecutorService
142144
import java.util.concurrent.TimeUnit
143145
import java.util.concurrent.atomic.AtomicBoolean
144146
import java.util.concurrent.atomic.AtomicReference
145-
import kotlin.time.Duration.Companion.seconds
146147

147148
/**
148149
* RUM feature class, which needs to be registered with Datadog SDK instance.
@@ -861,20 +862,15 @@ internal class RumFeature(
861862
// guaranteeing the real monitor is available regardless of which thread Rum.enable()
862863
// is called on (main thread for native Android, background thread for RN/Flutter).
863864
//
864-
// Additionally, the Activity has already completed its full lifecycle before the
865-
// SDK initialized (e.g. a cross-platform bridge delay). The view tracking strategy
866-
// missed onActivityStarted/onActivityResumed, so no RUM view has been started yet.
867-
// We replay the relevant lifecycle callback here so startView is queued before
868-
// AppStart/TTID.
869-
val capturedStrategy = viewTrackingStrategy
865+
// Cross-platform scenario (RN/Flutter): the Activity drew its first frame before the
866+
// SDK initialized, so the view tracking strategy never received onActivityStarted/
867+
// onActivityResumed for it — no RUM view is open. We call onLateActivityReady() so the
868+
// strategy can open the view before AppStart/TTID are sent.
869+
val capturedStrategy = viewTrackingStrategy as? ReplayableViewTrackingStrategy
870870
pendingPreLaunchAction = action@{
871871
val rumMonitor = GlobalRumMonitor.get(sdkCore) as? AdvancedRumMonitor ?: return@action
872872
scenario.activity.get()?.let { activity ->
873-
when (capturedStrategy) {
874-
is NavigationViewTrackingStrategy -> capturedStrategy.onActivityStarted(activity)
875-
is ActivityViewTrackingStrategy -> capturedStrategy.onActivityResumed(activity)
876-
else -> Unit
877-
}
873+
capturedStrategy?.onLateActivityReady(activity)
878874
}
879875
rumMonitor.sendAppStartEvent(scenario)
880876
rumMonitor.sendTTIDEvent(RumTTIDInfo(scenario = scenario, durationNs = durationNs))
@@ -900,6 +896,10 @@ internal class RumFeature(
900896
}
901897
)
902898

899+
@Suppress("UnsafeThirdPartyFunctionCall") // map is initialized empty
900+
private val firstFrameHandles =
901+
WeakHashMap<Activity, RumFirstDrawTimeReporter.Handle>()
902+
903903
override fun onAppStartupDetected(scenario: RumStartupScenario) {
904904
val activity = scenario.activity.get() ?: return
905905
val rumMonitor = GlobalRumMonitor.get(sdkCore) as? AdvancedRumMonitor ?: return
@@ -916,6 +916,10 @@ internal class RumFeature(
916916
subscribeToFirstFrameDrawn(pendingScenario, activity, rumMonitor, wasForwarded = true)
917917
}
918918

919+
override fun onActivityDestroyed(activity: Activity) {
920+
firstFrameHandles.remove(activity)?.unsubscribe()
921+
}
922+
919923
private fun subscribeToFirstFrameDrawn(
920924
scenario: RumStartupScenario,
921925
activity: Activity,
@@ -924,6 +928,7 @@ internal class RumFeature(
924928
) {
925929
val callback = object : RumFirstDrawTimeReporter.Callback {
926930
override fun onFirstFrameDrawn(timestampNs: Long) {
931+
firstFrameHandles.remove(activity)
927932
// Another activity may have already reported TTID
928933
val pending = rumAppStartupDetector?.getPendingScenario()
929934
if (pending !== scenario) return
@@ -940,7 +945,7 @@ internal class RumFeature(
940945
}
941946
}
942947

943-
rumFirstDrawTimeReporter.subscribeToFirstFrameDrawn(
948+
firstFrameHandles[activity] = rumFirstDrawTimeReporter.subscribeToFirstFrameDrawn(
944949
activity = activity,
945950
callback = callback
946951
)
@@ -950,7 +955,7 @@ internal class RumFeature(
950955
)
951956
}
952957

953-
@Suppress("ReturnCount", "StringLiteralDuplication", "UnsafeCallOnNullableType", "LongMethod")
958+
@Suppress("ReturnCount", "UnsafeCallOnNullableType")
954959
private fun constructScenario(collector: AppLaunchPreInitCollector): RumStartupScenario? {
955960
val activity = collector.activity?.get()
956961
if (activity == null) {
@@ -963,59 +968,32 @@ internal class RumFeature(
963968
}
964969

965970
if (!configuration.appStartupActivityPredicate.shouldTrackStartup(activity)) {
966-
return null
967-
}
968-
969-
val processStartTime = Time.fromNanoTime(collector.processStartNs, sdkCore.timeProvider)
970-
val activityOnCreateTime = Time.fromNanoTime(collector.activityOnCreateNs, sdkCore.timeProvider)
971-
val weakActivity = collector.activity!!
972-
val hasSavedInstanceStateBundle = collector.hasSavedInstanceState
973-
974-
return if (collector.isFirstActivityForProcess) {
975-
val gapNs = activityOnCreateTime.nanoTime - processStartTime.nanoTime
976-
if (gapNs > START_GAP_THRESHOLD_NS) {
977-
sdkCore.internalLogger.log(
978-
InternalLogger.Level.DEBUG,
979-
InternalLogger.Target.MAINTAINER,
980-
{
981-
"TTID: WarmFirstActivity" +
982-
" gapMs=${gapNs / NS_PER_MS} hasSavedInstanceState=$hasSavedInstanceStateBundle"
983-
}
984-
)
985-
RumStartupScenario.WarmFirstActivity(
986-
hasSavedInstanceStateBundle = hasSavedInstanceStateBundle,
987-
activity = weakActivity,
988-
appStartActivityOnCreateGapNs = gapNs,
989-
initialTime = activityOnCreateTime
990-
)
991-
} else {
992-
sdkCore.internalLogger.log(
993-
InternalLogger.Level.DEBUG,
994-
InternalLogger.Target.MAINTAINER,
995-
{
996-
"TTID: Cold" +
997-
" gapMs=${gapNs / NS_PER_MS} hasSavedInstanceState=$hasSavedInstanceStateBundle"
998-
}
999-
)
1000-
RumStartupScenario.Cold(
1001-
hasSavedInstanceStateBundle = hasSavedInstanceStateBundle,
1002-
activity = weakActivity,
1003-
appStartActivityOnCreateGapNs = gapNs,
1004-
initialTime = processStartTime
1005-
)
1006-
}
1007-
} else {
1008971
sdkCore.internalLogger.log(
1009972
InternalLogger.Level.DEBUG,
1010973
InternalLogger.Target.MAINTAINER,
1011-
{ "TTID: WarmAfterActivityDestroyed hasSavedInstanceState=$hasSavedInstanceStateBundle" }
1012-
)
1013-
RumStartupScenario.WarmAfterActivityDestroyed(
1014-
hasSavedInstanceStateBundle = hasSavedInstanceStateBundle,
1015-
activity = weakActivity,
1016-
initialTime = activityOnCreateTime
974+
{
975+
"TTID: pre-launch activity excluded by predicate — " +
976+
"falling back to default detector for subsequent activities"
977+
}
1017978
)
979+
return null
1018980
}
981+
982+
val processStartTime = Time.fromNanoTime(collector.processStartNs, sdkCore.timeProvider)
983+
val activityOnCreateTime = Time.fromNanoTime(collector.activityOnCreateNs, sdkCore.timeProvider)
984+
val scenario = RumStartupScenario.build(
985+
isFirstActivityForProcess = collector.isFirstActivityForProcess,
986+
hasSavedInstanceStateBundle = collector.hasSavedInstanceState,
987+
activity = collector.activity!!,
988+
processStartTime = processStartTime,
989+
activityOnCreateTime = activityOnCreateTime
990+
)
991+
sdkCore.internalLogger.log(
992+
InternalLogger.Level.DEBUG,
993+
InternalLogger.Target.MAINTAINER,
994+
{ "TTID: pre-launch scenario=${scenario::class.simpleName}" }
995+
)
996+
return scenario
1019997
}
1020998

1021999
// endregion
@@ -1058,7 +1036,6 @@ internal class RumFeature(
10581036

10591037
internal companion object {
10601038

1061-
private val START_GAP_THRESHOLD_NS = 10.seconds.inWholeNanoseconds
10621039
private const val NS_PER_MS = 1_000_000L
10631040

10641041
internal const val NDK_CRASH_BUS_MESSAGE_TYPE = "ndk_crash"

features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeaturePreInitStartupTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ internal class RumFeaturePreInitStartupTest {
373373

374374
// Then — view tracking strategy is primed before startup events so the RUM executor
375375
// has a StartView in the queue before AppStart/TTID
376-
verify(mockNavStrategy).onActivityStarted(mockActivity)
376+
verify(mockNavStrategy).onLateActivityReady(mockActivity)
377377
verify(mockRumMonitor, times(1)).sendAppStartEvent(any())
378378
verify(mockRumMonitor, times(1)).sendTTIDEvent(any())
379379
}
@@ -408,7 +408,7 @@ internal class RumFeaturePreInitStartupTest {
408408
testedFeature.pendingPreLaunchAction?.invoke()
409409

410410
// Then
411-
verify(mockActivityStrategy).onActivityResumed(mockActivity)
411+
verify(mockActivityStrategy).onLateActivityReady(mockActivity)
412412
verify(mockRumMonitor, times(1)).sendAppStartEvent(any())
413413
verify(mockRumMonitor, times(1)).sendTTIDEvent(any())
414414
}

0 commit comments

Comments
 (0)