Repository navigation
Description
Integration
sentry-android (through @sentry/react-native)
Version
sentry-android 8.61.0 (@sentry/react-native 8.31.0)
Steps to Reproduce
- React Native app (RN 0.81.5, new architecture) with a native stack (
react-native-screens4.17.1) andreactNavigationIntegration({ enableTimeToInitialDisplay: true }). - Push a screen onto the stack, wait for it to render, then go back. Repeat 11 times.
- Force a GC and take a heap dump (
am dumpheap -g).
Expected Result
The popped ScreenStackFragments are collected.
Actual Result
Each push leaves one ScreenStackFragment and its whole native view tree in memory. On a 256 MB heap device the app ends in OutOfMemoryError after long sessions.
Counts after a forced GC, 11 push/pop cycles:
| Class | Before | After |
|---|---|---|
com.swmansion.rnscreens.ScreenStackFragment |
3 | 14 |
io.sentry.android.core.internal.util.FirstDrawDoneListener |
1 | 12 |
com.facebook.react.views.view.ReactViewGroup |
143 | 638 |
Retention path:
ViewRootImpl
.mAttachInfo -> View$AttachInfo
.mTreeObserver -> ViewTreeObserver
.mOnGlobalLayoutListeners -> ViewTreeObserver$CopyOnWriteArray
.[n] -> FirstDrawDoneListener$2
.val$view -> com.swmansion.rnscreens.stack.views.ScreensCoordinatorLayout
.fragment -> com.swmansion.rnscreens.ScreenStackFragment
Cause
#5567 (fix for #5495) makes the OnGlobalLayoutListener call view.getViewTreeObserver().removeOnGlobalLayoutListener(this). That does not work for the views RNSentryReactFragmentLifecycleTracer registers:
onDraw()adds the layout listener to the window'sViewTreeObserver.- A static screen has no layout pass after its first draw, so the listener does not run yet.
- The next layout pass comes when the screen is popped. The fragment view is already detached, so
view.getViewTreeObserver()returns the view's own floating observer, not the window's. - The remove call does nothing. The listener stays on the window's observer and keeps the detached view alive.
Fix
#6152 fixes it: it posts the OnDrawListener removal to the main thread right after the draw and drops the layout listener. We applied the same change with patch-package (a copy of the listener in @sentry/react-native used by RNSentryReactFragmentLifecycleTracer) and ran the same test:
| Class | Before | After 11 cycles |
|---|---|---|
ScreenStackFragment |
3 | 4 |
FirstDrawDoneListener |
1 | 1 |
ReactViewGroup |
143 | 190 |
This matches enableTimeToInitialDisplay: false, so the leak is gone with TTID still on. The #6152 description says the old behaviour "isn't a memory leak" because everything dies with the activity. In a single-Activity React Native app the activity lives for the whole session, so it is an unbounded leak. We would be glad to see #6152 merged.
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsWaiting for: Product Owner
Activity
linear-code commentedon Oct 22, 2026
JAVA-791