Skip to content

Commit 3a7c8ba

Browse files
rubennortehuntie
authored andcommitted
Refactor FrameTimingsObserver for multi-activity support (facebook#55740)
Summary: Pull Request resolved: facebook#55740 Changelog: [internal] Previously, `FrameTimingsObserver` required a `Window` at construction time and could not track frames across activity changes. In multi-activity Android apps, navigating between activities would cause frame timing to stop being recorded from the new activity's window. This refactors `FrameTimingsObserver` to use a push-based setter pattern where `ReactHostImpl` calls `setCurrentWindow()` whenever the current activity changes (in `onHostResume` and `moveToHostDestroy`). The observer re-registers its frame metrics listener on the new window automatically. Key changes: - Remove `window` from constructor, add `setCurrentWindow()` method - Add `isStarted` flag to track observer state for proper listener registration on window changes - Mark `currentWindow` as `Volatile` for thread safety (UI thread writes, background coroutine reads) - Extract `registerFrameMetricsListener()` / `unregisterFrameMetricsListener()` helpers to reduce duplication Reviewed By: emily8rown Differential Revision: D94358266 fbshipit-source-id: 21fcc237dda599088ef49d1427a45fb9cf449a52
1 parent 31c51a1 commit 3a7c8ba

File tree

2 files changed

+44
-18
lines changed

2 files changed

+44
-18
lines changed

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/inspector/FrameTimingsObserver.kt

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,17 @@ import kotlinx.coroutines.launch
2626

2727
@DoNotStripAny
2828
internal class FrameTimingsObserver(
29-
private val window: Window,
3029
private val screenshotsEnabled: Boolean,
3130
private val onFrameTimingSequence: (sequence: FrameTimingSequence) -> Unit,
3231
) {
32+
private val isSupported = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
33+
3334
private val handler = Handler(Looper.getMainLooper())
3435
private var frameCounter: Int = 0
3536
private var bitmapBuffer: Bitmap? = null
37+
private var isStarted: Boolean = false
38+
39+
@Volatile private var currentWindow: Window? = null
3640

3741
private val frameMetricsListener =
3842
Window.OnFrameMetricsAvailableListener { _, frameMetrics, _dropCount ->
@@ -41,12 +45,30 @@ internal class FrameTimingsObserver(
4145
emitFrameTiming(beginTimestamp, endTimestamp)
4246
}
4347

48+
fun setCurrentWindow(window: Window?) {
49+
if (!isSupported || currentWindow === window) {
50+
return
51+
}
52+
53+
currentWindow?.removeOnFrameMetricsAvailableListener(frameMetricsListener)
54+
currentWindow = window
55+
if (isStarted) {
56+
currentWindow?.addOnFrameMetricsAvailableListener(frameMetricsListener, handler)
57+
}
58+
}
59+
4460
private suspend fun captureScreenshot(): String? = suspendCoroutine { continuation ->
4561
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
4662
continuation.resume(null)
4763
return@suspendCoroutine
4864
}
4965

66+
val window = currentWindow
67+
if (window == null) {
68+
continuation.resume(null)
69+
return@suspendCoroutine
70+
}
71+
5072
val decorView = window.decorView
5173
val width = decorView.width
5274
val height = decorView.height
@@ -102,17 +124,19 @@ internal class FrameTimingsObserver(
102124
}
103125

104126
fun start() {
105-
frameCounter = 0
106-
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
127+
if (!isSupported) {
107128
return
108129
}
109130

131+
frameCounter = 0
132+
isStarted = true
133+
110134
// Capture initial screenshot to ensure there's always at least one frame
111135
// recorded at the start of tracing, even if no UI changes occur
112136
val timestamp = System.nanoTime()
113137
emitFrameTiming(timestamp, timestamp)
114138

115-
window.addOnFrameMetricsAvailableListener(frameMetricsListener, handler)
139+
currentWindow?.addOnFrameMetricsAvailableListener(frameMetricsListener, handler)
116140
}
117141

118142
private fun emitFrameTiming(beginTimestamp: Long, endTimestamp: Long) {
@@ -135,11 +159,13 @@ internal class FrameTimingsObserver(
135159
}
136160

137161
fun stop() {
138-
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
162+
if (!isSupported) {
139163
return
140164
}
141165

142-
window.removeOnFrameMetricsAvailableListener(frameMetricsListener)
166+
isStarted = false
167+
168+
currentWindow?.removeOnFrameMetricsAvailableListener(frameMetricsListener)
143169
handler.removeCallbacksAndMessages(null)
144170

145171
bitmapBuffer?.recycle()

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.kt

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ public class ReactHostImpl(
250250
stateTracker.enterState("onHostResume(activity)")
251251

252252
currentActivity = activity
253+
frameTimingsObserver?.setCurrentWindow(activity?.window)
253254

254255
maybeEnableDevSupport(true)
255256
reactLifecycleStateManager.moveToOnHostResume(currentReactContext, activity)
@@ -851,6 +852,7 @@ public class ReactHostImpl(
851852
private fun moveToHostDestroy(currentContext: ReactContext?) {
852853
reactLifecycleStateManager.moveToOnHostDestroy(currentContext)
853854
currentActivity = null
855+
frameTimingsObserver?.setCurrentWindow(null)
854856
}
855857

856858
private fun raiseSoftException(
@@ -1571,18 +1573,16 @@ public class ReactHostImpl(
15711573
when (state) {
15721574
TracingState.ENABLED_IN_BACKGROUND_MODE,
15731575
TracingState.ENABLED_IN_CDP_MODE -> {
1574-
currentActivity?.window?.let { window ->
1575-
val observer =
1576-
FrameTimingsObserver(
1577-
window,
1578-
_screenshotsEnabled,
1579-
{ frameTimingsSequence ->
1580-
inspectorTarget.recordFrameTimings(frameTimingsSequence)
1581-
},
1582-
)
1583-
observer.start()
1584-
frameTimingsObserver = observer
1585-
}
1576+
val observer =
1577+
FrameTimingsObserver(
1578+
_screenshotsEnabled,
1579+
{ frameTimingsSequence ->
1580+
inspectorTarget.recordFrameTimings(frameTimingsSequence)
1581+
},
1582+
)
1583+
observer.setCurrentWindow(currentActivity?.window)
1584+
observer.start()
1585+
frameTimingsObserver = observer
15861586
}
15871587
TracingState.DISABLED -> {
15881588
frameTimingsObserver?.stop()

0 commit comments

Comments
 (0)