Harden lifecycle handling for Perf Monitor (#53905)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53905

Updates `PerfMonitorOverlayManager` so that it is minimally and correctly integrated in the `DevSupportManagerBase` reload cycle — attempting to fix a bug where the background profiling state on startup / subsequent packager connections would be out of sync.

Changelog: [Internal]

Reviewed By: rubennorte

Differential Revision: D83058519

fbshipit-source-id: 9a9bb32d3215ccf722aa39ca0ca943449f7ae62e
This commit is contained in:
Alex Hunt
2025-09-23 11:47:50 -07:00
committed by Facebook GitHub Bot
parent b9e6e349f5
commit f4b6131850
3 changed files with 32 additions and 46 deletions
@@ -78,7 +78,6 @@ import java.io.File
import java.net.MalformedURLException
import java.net.URL
import java.util.Locale
import javax.inject.Provider
public abstract class DevSupportManagerBase(
protected val applicationContext: Context,
@@ -185,6 +184,7 @@ public abstract class DevSupportManagerBase(
}
private var perfMonitorOverlayManager: PerfMonitorOverlayManager? = null
private var perfMonitorInitialized = false
private var tracingStateProvider: TracingStateProvider? = null
init {
@@ -222,13 +222,6 @@ public abstract class DevSupportManagerBase(
perfMonitorOverlayManager =
PerfMonitorOverlayManager(
reactInstanceDevHelper,
Provider {
val context = reactInstanceDevHelper.currentActivity
if (context == null || context.isFinishing) {
return@Provider null
}
context
},
{ openDebugger(DebuggerFrontendPanelName.PERFORMANCE.toString()) },
)
}
@@ -552,14 +545,16 @@ public abstract class DevSupportManagerBase(
}
override fun onNewReactContextCreated(reactContext: ReactContext) {
resetCurrentContext(reactContext)
if (reactInstanceDevHelper is PerfMonitorDevHelper) {
if (!perfMonitorInitialized && reactInstanceDevHelper is PerfMonitorDevHelper) {
perfMonitorOverlayManager?.let { manager ->
reactInstanceDevHelper.inspectorTarget?.addPerfMonitorListener(manager)
}
perfMonitorOverlayManager?.enable()
perfMonitorOverlayManager?.startBackgroundTrace()
perfMonitorInitialized = true
}
resetCurrentContext(reactContext)
}
override fun onReactInstanceDestroyed(reactContext: ReactContext) {
@@ -877,8 +872,6 @@ public abstract class DevSupportManagerBase(
devLoadingViewManager?.showMessage("Reloading...")
}
perfMonitorOverlayManager?.reset()
devServerHelper.openPackagerConnection(
javaClass.simpleName,
object : PackagerCommandListener {
@@ -928,7 +921,7 @@ public abstract class DevSupportManagerBase(
hideRedboxDialog()
hideDevOptionsDialog()
devLoadingViewManager?.hide()
perfMonitorOverlayManager?.reset()
perfMonitorOverlayManager?.disable()
devServerHelper.closePackagerConnection()
}
@@ -7,11 +7,15 @@
package com.facebook.react.devsupport.perfmonitor
import android.app.Activity
/**
* Interface implemented by [com.facebook.react.runtime.ReactHostImplDevHelper] exposing additional
* hooks used to implement the V2 Perf Monitor overlay (experimental).
*/
internal interface PerfMonitorDevHelper {
public val currentActivity: Activity?
/**
* The inspector target object. Matches the lifetime of the ReactHost. May be null if modern JS
* debugging is disabled.
@@ -7,42 +7,24 @@
package com.facebook.react.devsupport.perfmonitor
import android.content.Context
import com.facebook.react.bridge.UiThreadUtil
import com.facebook.react.devsupport.interfaces.TracingState
import javax.inject.Provider
internal class PerfMonitorOverlayManager(
private val devHelper: PerfMonitorDevHelper,
private val contextProvider: Provider<Context?>,
private val onRequestOpenDevTools: () -> Unit,
) : PerfMonitorUpdateListener {
private var enabled: Boolean = false
private var initialized: Boolean = false
private var view: PerfMonitorOverlayView? = null
private var tracingState: TracingState = TracingState.ENABLEDINCDPMODE
private fun init() {
if (initialized || !enabled) {
return
}
UiThreadUtil.runOnUiThread {
val context = contextProvider.get() ?: return@runOnUiThread
view = PerfMonitorOverlayView(context, ::handleRecordingButtonPress)
// Start background tracing
devHelper.inspectorTarget?.resumeBackgroundTrace()
view?.show()
initialized = true
}
}
/** Enable the Perf Monitor overlay. Will be shown when updates are received. */
/** Enable the Perf Monitor overlay. */
fun enable() {
enabled = true
init()
UiThreadUtil.runOnUiThread {
val context = devHelper.currentActivity ?: return@runOnUiThread
view = PerfMonitorOverlayView(context, ::handleRecordingButtonPress)
}
}
/** Disable the Perf Monitor overlay. Will remain hidden when updates are received. */
@@ -52,24 +34,31 @@ internal class PerfMonitorOverlayManager(
enabled = false
}
/** Reset the Perf Monitor overlay, e.g. after a reload. */
fun reset() {
// Update with current recording state
onRecordingStateChanged(
devHelper.inspectorTarget?.getTracingState() ?: TracingState.ENABLEDINCDPMODE
)
/** Start background trace recording. */
fun startBackgroundTrace() {
if (!enabled) {
return
}
devHelper.inspectorTarget?.let { target ->
target.resumeBackgroundTrace()
onRecordingStateChanged(target.getTracingState())
}
}
override fun onRecordingStateChanged(state: TracingState) {
tracingState = state
view?.updateRecordingState(state)
UiThreadUtil.runOnUiThread {
view?.updateRecordingState(state)
view?.show()
}
}
private fun handleRecordingButtonPress() {
when (tracingState) {
TracingState.ENABLEDINBACKGROUNDMODE -> {
devHelper.inspectorTarget?.let {
if (!it.pauseAndAnalyzeBackgroundTrace()) {
devHelper.inspectorTarget?.let { target ->
if (!target.pauseAndAnalyzeBackgroundTrace()) {
onRequestOpenDevTools()
}
}