feat: TTID pre-launch capture for cross-platform SDKs support#3635
Draft
sbarrio wants to merge 2 commits into
Draft
feat: TTID pre-launch capture for cross-platform SDKs support#3635sbarrio wants to merge 2 commits into
sbarrio wants to merge 2 commits into
Conversation
3 tasks
29d98ba to
75a5402
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Builds on the work developed by @marco-saia-datadog here: #3371 and updates it so it properly works and reports TTID on Android on cross platform SDKs that include the new
com.datadoghq:dd-sdk-android-rum-prelaunchmodule.This PR adds three things:
New
AppLaunchPreInitCollectorindd-sdk-android-internal. It's a singleton that collects timing data before the SDK initializes — process start time, firstActivity.onCreate, and first frame drawn. State transitions use atomic compare-and-swap (NOT_INSTALLED → IDLE → CAPTURING / CLAIMED → COMPLETE) so the collector and the SDK can't race on who drives startup.New
dd-sdk-android-rum-prelaunchmodule with a singleContentProvider(AppLaunchCollectorProvider) that installs the collector automatically at process start. No customer code required.RumFeature.initRumAppStartupDetector()now checks collector state on init: if data is already captured, read it; if capture is in progress, subscribe; if not installed or the SDK got there first, fall back to the existingRumAppStartupDetectorflow unchanged.RumFirstDrawTimeReporterandWindowCallbacksRegistryare also moved todd-sdk-android-internal, since both paths need them now. The originals indd-sdk-android-rumare deleted.On top of Marco's work, this PR fixes two issues that prevented the feature from working correctly:
1. TTID/TTFD events not assigned to a view
The app start and TTID events were being sent before the first RUM view was started, so they weren't attached to any view in the session. The fix defers those events via a
pendingPreLaunchActionthat is dispatched on the main thread afterGlobalRumMonitor.registerIfAbsent()runs, ensuring the view is already open when the events arrive.2. Memory leak in
RumFirstDrawTimeReporterImplWhen subscribing to first-frame events for an activity that never calls
setContentView()(e.g. an interstitial that just callsstartActivity + finish()),WindowCallbacksRegistrywraps theActivity'sWindow.Callbackand stores it in aWeakHashMap<Activity, WindowCallback>. BecauseActivityitself implementsWindow.Callback,WindowCallbackends up holding a strong reference back to the map key, preventing GC from ever collecting it. TheWindowCallbackListenerthat would clean up this entry only fires ononContentChanged— which never happens ifsetContentViewis never called. This causedNoLeakAssertionFailedErrorin all TTID auto-forwarding integration tests.The fix registers an
Application.ActivityLifecycleCallbacksalongside eachWindowCallbackListener. If theActivityis destroyed beforesetContentViewis called, the callback removes the listener and breaks the strong reference. Both theActivityand the listener are held asWeakReferenceinside the cleanup callback so the registration itself creates no new retention path.Motivation
React Native and Flutter initialize the Datadog SDK from JS/Dart, well after the first activity has launched. TTID goes unreported for those SDKs unless you add native initialization (
DdSdkNativeInitialization.initFromNative()), which means native Android code in a cross-platform project.The collector sidesteps this. By the time the SDK starts, the timing data is already waiting.
Native Android apps are unaffected. If the SDK initializes before the first activity, it claims the collector and the existing
RumAppStartupDetectorpath runs exactly as before.Additional Notes
dd-sdk-android-rum-prelaunchis opt-in. Cross-platform SDKs depend on it; native apps don't.RumFirstDrawTimeReporterImpltakes an injectablewarnLoggerlambda.RumFeaturepasses one that routes throughsdkCore.internalLoggerwithTarget.TELEMETRY + Target.USER. The pre-init path defaults toLog.wsince there's no SDK available at that point.Review checklist (to be filled by reviewers)