Skip to content

Preserve lazy ViewManager initialization in UIManager - #58097

Open
fairysword wants to merge 1 commit into
react:mainfrom
fairysword:fix/uimanager-lazy-viewmanager-initialization
Open

Preserve lazy ViewManager initialization in UIManager#58097
fairysword wants to merge 1 commit into
react:mainfrom
fairysword:fix/uimanager-lazy-viewmanager-initialization

Conversation

@fairysword

@fairysword fairysword commented Aug 24, 2026

Copy link
Copy Markdown

Summary:

  • Preserve lazy ViewManager property descriptors when composing UIManager.
  • Avoid eagerly invoking lazy ViewManager getters and unnecessary getConstantsForViewManager calls during Bridgeless initialization.
  • Add regression coverage for lazy, own, enumerable and one-time getter behavior.

Changelog:

[ANDROID] [FIXED] - Avoid unnecessary getConstantsForViewManager calls during UIManager initialization.

Test Plan:

  • yarn flow-check
  • yarn test packages/react-native/Libraries/ReactNative --runInBand
  • yarn lint --quiet
  • yarn format-check
  • Verified with Android RNTester offlineDebug that startup no longer triggers eager getConstantsForViewManager calls.

Copy UIManager implementation property descriptors instead of spreading UIManagerImpl, so Bridgeless initialization does not eagerly invoke lazy ViewManager getters or trigger unnecessary getConstantsForViewManager calls.

Add regression coverage verifying that ViewManager descriptors remain lazy, own, and enumerable, and that each getter is evaluated only once on first access.
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Aug 24, 2026
@fairysword

Copy link
Copy Markdown
Author

UIManager lazy ViewManager initialization optimization

Test environment:

  • RNTester offlineDebug
  • Debug native code with a Hermes bundle loaded from APK assets; Metro/dev support disabled
  • Same Android device (V2338A, Android 16)
  • logcat cleared and the app force-stopped before each cold start
  • The bundle task was forced to rerun before each APK was installed

Metric definitions:

  • Bundle Load = timestamp(RUN_JS_BUNDLE_END) - timestamp(RUN_JS_BUNDLE_START)
  • CONTENT_APPEARED = timestamp(CONTENT_APPEARED) - timestamp(RUN_JS_BUNDLE_START)

1. Temporary ReactInstance instrumentation

The following debug-only instrumentation was added locally to ReactInstance.kt. It is not part of the proposed patch.

import android.util.Log

private fun createConstants(
    viewManagers: List<ViewManager<in Nothing, in Nothing>>,
    customDirectEvents: MutableMap<String, Any>?,
): MutableMap<String, Any> {
  val startNanos = System.nanoTime()
  // Existing implementation...
  try {
    return UIManagerModuleConstantsHelper.createConstants(
        viewManagers,
        null,
        customDirectEvents,
    )
  } finally {
    // Existing marker cleanup...
    if (BuildConfig.DEBUG) {
      Log.d(
          "RNUIManagerPerf",
          "getConstants(eagerViewManagers=${viewManagers.size}) took ${(System.nanoTime() - startNanos) / 1_000_000.0}ms",
      )
    }
  }
}

private fun getConstantsForViewManager(
    viewManager: ViewManager<*, *>,
    customDirectEvents: MutableMap<String, Any>,
): NativeMap {
  val startNanos = System.nanoTime()
  // Existing implementation...
  try {
    val viewManagerConstants =
        UIManagerModuleConstantsHelper.createConstantsForViewManager(
            viewManager,
            null,
            null,
            null,
            customDirectEvents,
        )
    return Arguments.makeNativeMap(viewManagerConstants)
  } finally {
    // Existing trace cleanup...
    if (BuildConfig.DEBUG) {
      Log.d(
          "RNUIManagerPerf",
          "getConstantsForViewManager(${viewManager.name}) took ${(System.nanoTime() - startNanos) / 1_000_000.0}ms",
      )
    }
  }
}

2. Before/after logs

Before: object spread

With ...UIManagerImpl, importing UIManager reads every enumerable lazy ViewManager property. This synchronously calls getConstantsForViewManager for all 20 registered lazy ViewManagers during startup.

D/RNUIManagerPerf: getConstants(eagerViewManagers=0) took 0.937031ms
D/RNUIManagerPerf: getConstantsForViewManager(RCTSafeAreaView) took 15.836355ms
D/RNUIManagerPerf: getConstantsForViewManager(RCTModalHostView) took 6.342136ms
D/RNUIManagerPerf: getConstantsForViewManager(AndroidProgressBar) took 4.09651ms
D/RNUIManagerPerf: getConstantsForViewManager(AndroidHorizontalScrollView) took 6.418177ms
D/RNUIManagerPerf: getConstantsForViewManager(RCTImageView) took 5.935573ms
D/RNUIManagerPerf: getConstantsForViewManager(RCTText) took 5.849114ms
D/RNUIManagerPerf: getConstantsForViewManager(AndroidHorizontalScrollContentView) took 7.902032ms
D/RNUIManagerPerf: getConstantsForViewManager(RNTMyLegacyNativeView) took 3.298698ms
D/RNUIManagerPerf: getConstantsForViewManager(RNTReportFullyDrawnView) took 3.527865ms
D/RNUIManagerPerf: getConstantsForViewManager(UnimplementedNativeView) took 3.425677ms
D/RNUIManagerPerf: getConstantsForViewManager(RCTScrollView) took 6.097032ms
D/RNUIManagerPerf: getConstantsForViewManager(RCTView) took 7.669635ms
D/RNUIManagerPerf: getConstantsForViewManager(AndroidDrawerLayout) took 3.646875ms
D/RNUIManagerPerf: getConstantsForViewManager(AndroidSwitch) took 3.287812ms
D/RNUIManagerPerf: getConstantsForViewManager(AndroidSwipeRefreshLayout) took 3.423437ms
D/RNUIManagerPerf: getConstantsForViewManager(AndroidPopupMenu) took 2.970105ms
D/RNUIManagerPerf: getConstantsForViewManager(RNTMyNativeView) took 2.043542ms
D/RNUIManagerPerf: getConstantsForViewManager(AndroidTextInput) took 6.461354ms
D/RNUIManagerPerf: getConstantsForViewManager(RCTSelectableText) took 3.281875ms
D/RNUIManagerPerf: getConstantsForViewManager(VirtualView) took 2.945833ms

The 20 getConstantsForViewManager calls took 104.46 ms cumulatively.

Startup timing from the same run:

Bundle Load: 161ms
CONTENT_APPEARED: 356ms

After: property descriptor composition

After preserving the lazy property descriptors instead of reading their values, startup no longer calls getConstantsForViewManager:

D/RNUIManagerPerf: getConstants(eagerViewManagers=0) took 3.361563ms

Result:

  • getConstantsForViewManager startup calls: 20 → 0
  • Cumulative synchronous ViewManager configuration work: 104.46 ms → 0 ms

Startup timing from the same run:

Bundle Load: 49ms
CONTENT_APPEARED: 289ms

The marker timings above are one cold-start sample and are included as supporting evidence. The deterministic regression signal is the removal of all 20 synchronous getConstantsForViewManager calls during UIManager module initialization.

@facebook-github-tools facebook-github-tools Bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label Aug 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant