Skip to content

Cache UIManager constants in bridgeless new architecture on Android (Improve app start on low end/mid android by 150-300ms#56078

Open
Szymon20000 wants to merge 1 commit intofacebook:mainfrom
Szymon20000:szymon/uimanager-cache-kotlin-pr
Open

Cache UIManager constants in bridgeless new architecture on Android (Improve app start on low end/mid android by 150-300ms#56078
Szymon20000 wants to merge 1 commit intofacebook:mainfrom
Szymon20000:szymon/uimanager-cache-kotlin-pr

Conversation

@Szymon20000
Copy link

Summary:

Screenshot 2026-03-12 at 21 55 25 This is what I noticed in many apps on the new arch. In paper architecture we created constants on UIManager thread so it didn't affect TTI however on the new arch it happens on js thread. This pr caches constants on first run (usually on onboarding and speeds up all subsequent runs.) The best solution would be to get rid of constants and just generate them at build time and put in hermes bytecode. I know that something like that is already on the roadmap but this seems to be a good temporary solution :)

Motivation: startup in bridgeless + new architecture was spending significant time in UIManager host functions (getConstants / getConstantsForViewManager) even on repeated launches.

This PR adds Android-side caching for UIManager constants and lazy view manager constants, preloads them early on app startup, and reuses them from UIConstantsProviderBinding to avoid recomputing on subsequent runs.

It also adds cache identity validation so stale cache is not reused after app/bundle changes (including OTA/file-bundle changes). Cache is only used when identity matches; otherwise it safely falls back to normal computation and refreshes cache.

Changelog:

[ANDROID] [CHANGED] - Cache and preload UIManager constants in bridgeless new architecture and invalidate cache when app/bundle identity changes.

Test Plan:

  1. Build/install RNTester release on device:

    • ./gradlew :packages:rn-tester:android:app:installRelease
    • Result: BUILD SUCCESSFUL and Installed on 1 device.
  2. Capture Hermes sampling profiles on release (two sequential launches, 10s window via react-native-release-profiler):

    • Launch/pull command used:
      • yarn workspace @react-native/tester react-native-release-profiler --fromDownload --raw --appId com.facebook.react.uiapp
    • Artifacts:
      • /Users/szymonkapala/work/react-native/.tmp/rntester-profiler-verify-20260312-214548/run1.cpuprofile.txt
      • /Users/szymonkapala/work/react-native/.tmp/rntester-profiler-verify-20260312-214548/run2.cpuprofile.txt
  3. Profile comparison (sample-based):

    • Run 1 (expected cold): getConstantsForViewManager present (41 samples), getConstants present (1 sample).
    • Run 2 (expected cache hit): getConstantsForViewManager 0 samples, getConstants 0 samples.

This verifies first-run population + second-run reuse behavior with the new cache identity checks.

@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 Mar 12, 2026
@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 Mar 12, 2026
@javache
Copy link
Member

javache commented Mar 13, 2026

Would it make sense to cache this in JS instead? We’d also want to have a feature flag for this.

@Szymon20000
Copy link
Author

Would it make sense to cache this in JS instead? We’d also want to have a feature flag for this.

That sounds like a cleaner solution. I first use this optimisation with birdgeless off environment and then UIManager created constants in constructor. If in bridgeless the old paper UIManager is no longer created I think js would be cleaner. Let me test that and add a feature flag.

@Szymon20000
Copy link
Author

@javache
I implemented and tested JS version and got similar improvement. A bit worse as in the native one we load cache when process starts so everything is ready when we need it. The main problem I have with js approach is persistence. We don't want to make RN dependent on MMKV or other synchronous storages so we would need to implement a turbo module. Please let me know if there already is a built in solution in RN we could use :). I think keeping it on the native side can be a bit cleaner because of that but please let me know what do you think. I can hide it behind a feature flag and go native or js way depending on your response.

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.

2 participants