Make nvc interop layer lazy (#42218)

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

## Changes
This diff makes the native view config interop layer on Android lazy.

As in: ViewManagers that were registered lazily with React Native will no longer be eagerly initialized by the nvc interop layer.

Changes to UIManager apis:
- UIManager.getConstants() now **only** contains the view configs for the eager view managers.
- UIManager.getConstantsForViewManager(name): lazily load view configs for lazy components
- UIManager.getDefaultEventTypes(): load default event types
- UIManager.getConstants().LazyViewManagersEnabled: true, if there are lazy view managers
- UIManager.getConstants().ViewManagerNames: a list of the lazy view managers

Changelog: [Internal]

Reviewed By: dmytrorykun

Differential Revision: D52399280

fbshipit-source-id: d9cd46de0507ecfe6cca5595a237e1063f60fa62
This commit is contained in:
Ramanpreet Nara
2024-01-10 12:35:33 -08:00
committed by Facebook GitHub Bot
parent b6bb2691f9
commit cc1258cb7d
11 changed files with 287 additions and 71 deletions
@@ -14,6 +14,7 @@ import type {RootTag} from '../Types/RootTagTypes';
import type {UIManagerJSInterface} from '../Types/UIManagerJSInterface';
import {unstable_hasComponent} from '../NativeComponent/NativeComponentRegistryUnstable';
import defineLazyObjectProperty from '../Utilities/defineLazyObjectProperty';
import Platform from '../Utilities/Platform';
import {getFabricUIManager} from './FabricUIManager';
import nullthrows from 'nullthrows';
@@ -28,10 +29,10 @@ function raiseSoftError(methodName: string, details?: string): void {
const getUIManagerConstants: ?() => {[viewManagerName: string]: Object} =
global.RN$LegacyInterop_UIManager_getConstants;
const getUIManagerConstantsCache = (function () {
const getUIManagerConstantsCached = (function () {
let wasCalledOnce = false;
let result = {};
return () => {
return (): {[viewManagerName: string]: Object} => {
if (!wasCalledOnce) {
result = nullthrows(getUIManagerConstants)();
wasCalledOnce = true;
@@ -40,6 +41,24 @@ const getUIManagerConstantsCache = (function () {
};
})();
const getConstantsForViewManager: ?(viewManagerName: string) => Object =
global.RN$LegacyInterop_UIManager_getConstantsForViewManager;
const getDefaultEventTypes: ?() => Object =
global.RN$LegacyInterop_UIManager_getDefaultEventTypes;
const getDefaultEventTypesCached = (function () {
let wasCalledOnce = false;
let result = null;
return (): Object => {
if (!wasCalledOnce) {
result = nullthrows(getDefaultEventTypes)();
wasCalledOnce = true;
}
return result;
};
})();
/**
* UIManager.js overrides these APIs.
* Pull them out from the BridgelessUIManager implementation. So, we can ignore them.
@@ -133,10 +152,18 @@ const UIManagerJSUnusedAPIs = {
const UIManagerJSPlatformAPIs = Platform.select({
android: {
getConstantsForViewManager: (viewManagerName: string): Object => {
if (getConstantsForViewManager) {
return getConstantsForViewManager(viewManagerName);
}
raiseSoftError('getConstantsForViewManager');
return {};
},
getDefaultEventTypes: (): Array<string> => {
if (getDefaultEventTypes) {
return getDefaultEventTypesCached();
}
raiseSoftError('getDefaultEventTypes');
return [];
},
@@ -259,7 +286,15 @@ const UIManagerJS: UIManagerJSInterface & {[string]: any} = {
...UIManagerJSUnusedAPIs,
getViewManagerConfig: (viewManagerName: string): mixed => {
if (getUIManagerConstants) {
return getUIManagerConstantsCache()[viewManagerName];
const constants = getUIManagerConstantsCached();
if (
!constants[viewManagerName] &&
UIManagerJS.getConstantsForViewManager
) {
constants[viewManagerName] =
UIManagerJS.getConstantsForViewManager(viewManagerName);
}
return constants[viewManagerName];
} else {
raiseSoftError(
`getViewManagerConfig('${viewManagerName}')`,
@@ -273,7 +308,7 @@ const UIManagerJS: UIManagerJSInterface & {[string]: any} = {
},
getConstants: (): Object => {
if (getUIManagerConstants) {
return getUIManagerConstantsCache();
return getUIManagerConstantsCached();
} else {
raiseSoftError('getConstants');
return null;
@@ -320,9 +355,18 @@ const UIManagerJS: UIManagerJSInterface & {[string]: any} = {
};
if (getUIManagerConstants) {
Object.keys(getUIManagerConstantsCache()).forEach(viewConfigName => {
UIManagerJS[viewConfigName] = getUIManagerConstantsCache()[viewConfigName];
Object.keys(getUIManagerConstantsCached()).forEach(viewConfigName => {
UIManagerJS[viewConfigName] = getUIManagerConstantsCached()[viewConfigName];
});
if (UIManagerJS.getConstants().ViewManagerNames) {
UIManagerJS.getConstants().ViewManagerNames.forEach(viewManagerName => {
defineLazyObjectProperty(UIManagerJS, viewManagerName, {
get: () =>
nullthrows(UIManagerJS.getConstantsForViewManager)(viewManagerName),
});
});
}
}
module.exports = UIManagerJS;
@@ -22,6 +22,7 @@ import com.facebook.react.bridge.JSBundleLoader;
import com.facebook.react.bridge.JSBundleLoaderDelegate;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.NativeArray;
import com.facebook.react.bridge.NativeMap;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactNoCrashSoftException;
import com.facebook.react.bridge.ReactSoftExceptionLogger;
@@ -54,9 +55,9 @@ import com.facebook.react.uimanager.ComponentNameResolver;
import com.facebook.react.uimanager.ComponentNameResolverManager;
import com.facebook.react.uimanager.DisplayMetricsHolder;
import com.facebook.react.uimanager.IllegalViewOperationException;
import com.facebook.react.uimanager.UIConstantsProvider;
import com.facebook.react.uimanager.UIConstantsProviderManager;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.UIManagerModuleConstantsHelper;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.uimanager.ViewManagerRegistry;
import com.facebook.react.uimanager.ViewManagerResolver;
@@ -245,6 +246,8 @@ final class ReactInstance {
// initialized.
// This happens inside getTurboModuleManagerDelegate getter.
if (ReactFeatureFlags.useNativeViewConfigsInBridgelessMode) {
Map<String, Object> customDirectEvents = new HashMap<>();
mUIConstantsProviderManager =
new UIConstantsProviderManager(
// Use unbuffered RuntimeExecutor to install binding
@@ -255,21 +258,36 @@ final class ReactInstance {
// 2. genericBubblingEventTypes.
// 3. genericDirectEventTypes.
// We want to match this beahavior.
(UIConstantsProvider)
() -> {
List<ViewManager> viewManagers = new ArrayList<ViewManager>();
() -> {
return (NativeMap)
Arguments.makeNativeMap(
UIManagerModuleConstantsHelper.getDefaultExportableEventTypes());
},
(String viewManagerName) -> {
ViewManager viewManager = mViewManagerResolver.getViewManager(viewManagerName);
if (viewManager == null) {
return null;
}
return (NativeMap)
UIManagerModule.getConstantsForViewManager(viewManager, customDirectEvents);
},
() -> {
List<ViewManager> viewManagers =
new ArrayList<ViewManager>(
mViewManagerResolver.getEagerViewManagerMap().values());
synchronized (mViewManagerResolver) {
for (String viewManagerName : mViewManagerResolver.getViewManagerNames()) {
viewManagers.add(mViewManagerResolver.getViewManager(viewManagerName));
}
}
Map<String, Object> constants =
UIManagerModule.createConstants(viewManagers, null, customDirectEvents);
Map<String, Object> constants =
UIManagerModule.createConstants(
viewManagers, new HashMap<>(), new HashMap<>());
return Arguments.makeNativeMap(constants);
});
Collection<String> lazyViewManagers =
mViewManagerResolver.getLazyViewManagerNames();
if (lazyViewManagers.size() > 0) {
constants.put("ViewManagerNames", new ArrayList<>(lazyViewManagers));
constants.put("LazyViewManagersEnabled", true);
}
return Arguments.makeNativeMap(constants);
});
}
EventBeatManager eventBeatManager = new EventBeatManager();
@@ -528,7 +546,7 @@ final class ReactInstance {
return allViewManagerNames;
}
private Map<String, ViewManager> getEagerViewManagerMap() {
public synchronized Map<String, ViewManager> getEagerViewManagerMap() {
if (mEagerViewManagerMap != null) {
return mEagerViewManagerMap;
}
@@ -574,7 +592,7 @@ final class ReactInstance {
return null;
}
private Collection<String> getLazyViewManagerNames() {
public synchronized Collection<String> getLazyViewManagerNames() {
Set<String> uniqueNames = new HashSet<>();
for (ReactPackage reactPackage : mReactPackages) {
if (reactPackage instanceof ViewManagerOnDemandReactPackage) {
@@ -1,18 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.uimanager;
import com.facebook.proguard.annotations.DoNotStripAny;
import com.facebook.react.bridge.NativeMap;
@DoNotStripAny
public interface UIConstantsProvider {
/* Returns UIManager's constants. */
NativeMap getConstants();
}
@@ -10,8 +10,10 @@ package com.facebook.react.uimanager;
import com.facebook.jni.HybridData;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.proguard.annotations.DoNotStripAny;
import com.facebook.react.bridge.NativeMap;
import com.facebook.react.bridge.RuntimeExecutor;
import com.facebook.soloader.SoLoader;
import javax.annotation.Nullable;
@DoNotStripAny
public class UIConstantsProviderManager {
@@ -25,17 +27,47 @@ public class UIConstantsProviderManager {
private final HybridData mHybridData;
public UIConstantsProviderManager(
RuntimeExecutor runtimeExecutor, Object uiConstantsProviderManager) {
mHybridData = initHybrid(runtimeExecutor, uiConstantsProviderManager);
RuntimeExecutor runtimeExecutor,
DefaultEventTypesProvider defaultEventTypesProvider,
ConstantsForViewManagerProvider viewManagerConstantsProvider,
ConstantsProvider constantsProvider) {
mHybridData =
initHybrid(
runtimeExecutor,
defaultEventTypesProvider,
viewManagerConstantsProvider,
constantsProvider);
installJSIBindings();
}
private native HybridData initHybrid(
RuntimeExecutor runtimeExecutor, Object uiConstantsProviderManager);
RuntimeExecutor runtimeExecutor,
DefaultEventTypesProvider defaultEventTypesProvider,
ConstantsForViewManagerProvider viewManagerConstantsProvider,
ConstantsProvider constantsProvider);
private native void installJSIBindings();
private static void staticInit() {
SoLoader.loadLibrary("uimanagerjni");
}
@DoNotStripAny
public static interface DefaultEventTypesProvider {
/* Returns UIManager's constants. */
NativeMap getDefaultEventTypes();
}
@DoNotStripAny
public static interface ConstantsForViewManagerProvider {
/* Returns UIManager's constants. */
@Nullable
NativeMap getConstantsForViewManager(String viewManagerName);
}
@DoNotStripAny
public static interface ConstantsProvider {
/* Returns UIManager's constants. */
NativeMap getConstants();
}
}
@@ -254,15 +254,20 @@ public class UIManagerModule extends ReactContextBaseJavaModule
return null;
}
return getConstantsForViewManager(targetView, mCustomDirectEvents);
}
public static @Nullable WritableMap getConstantsForViewManager(
ViewManager viewManager, Map<String, Object> customDirectEvents) {
SystraceMessage.beginSection(
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "UIManagerModule.getConstantsForViewManager")
.arg("ViewManager", targetView.getName())
.arg("ViewManager", viewManager.getName())
.arg("Lazy", true)
.flush();
try {
Map<String, Object> viewManagerConstants =
UIManagerModuleConstantsHelper.createConstantsForViewManager(
targetView, null, null, null, mCustomDirectEvents);
viewManager, null, null, null, customDirectEvents);
if (viewManagerConstants != null) {
return Arguments.makeNativeMap(viewManagerConstants);
}
@@ -24,7 +24,7 @@ import java.util.Set;
* Helps generate constants map for {@link UIManagerModule} by collecting and merging constants from
* registered view managers.
*/
/* package */ class UIManagerModuleConstantsHelper {
public class UIManagerModuleConstantsHelper {
private static final String TAG = "UIManagerModuleConstantsHelper";
private static final String BUBBLING_EVENTS_KEY = "bubblingEventTypes";
private static final String DIRECT_EVENTS_KEY = "directEventTypes";
@@ -43,7 +43,7 @@ import java.util.Set;
return constants;
}
/* package */ static Map<String, Object> getDefaultExportableEventTypes() {
public static Map<String, Object> getDefaultExportableEventTypes() {
return MapBuilder.<String, Object>of(
BUBBLING_EVENTS_KEY, UIManagerModuleConstants.getBubblingEventTypeConstants(),
DIRECT_EVENTS_KEY, UIManagerModuleConstants.getDirectEventTypeConstants());
@@ -22,18 +22,34 @@ using namespace facebook::jni;
UIConstantsProviderManager::UIConstantsProviderManager(
jni::alias_ref<UIConstantsProviderManager::javaobject> jThis,
RuntimeExecutor runtimeExecutor,
jni::alias_ref<jobject> uiConstantsProvider)
jni::alias_ref<DefaultEventTypesProvider::javaobject>
defaultExportableEventTypesProvider,
jni::alias_ref<ConstantsForViewManagerProvider::javaobject>
constantsForViewManagerProvider,
jni::alias_ref<ConstantsProvider::javaobject> constantsProvider)
: javaPart_(jni::make_global(jThis)),
runtimeExecutor_(runtimeExecutor),
uiConstantsProvider_(jni::make_global(uiConstantsProvider)) {}
defaultExportableEventTypesProvider_(
jni::make_global(defaultExportableEventTypesProvider)),
constantsForViewManagerProvider_(
jni::make_global(constantsForViewManagerProvider)),
constantsProvider_(jni::make_global(constantsProvider)) {}
jni::local_ref<UIConstantsProviderManager::jhybriddata>
UIConstantsProviderManager::initHybrid(
jni::alias_ref<jhybridobject> jThis,
jni::alias_ref<JRuntimeExecutor::javaobject> runtimeExecutor,
jni::alias_ref<jobject> uiConstantsProvider) {
jni::alias_ref<DefaultEventTypesProvider::javaobject>
defaultExportableEventTypesProvider,
jni::alias_ref<ConstantsForViewManagerProvider::javaobject>
constantsForViewManagerProvider,
jni::alias_ref<ConstantsProvider::javaobject> constantsProvider) {
return makeCxxInstance(
jThis, runtimeExecutor->cthis()->get(), uiConstantsProvider);
jThis,
runtimeExecutor->cthis()->get(),
defaultExportableEventTypesProvider,
constantsForViewManagerProvider,
constantsProvider);
}
void UIConstantsProviderManager::registerNatives() {
@@ -46,18 +62,33 @@ void UIConstantsProviderManager::registerNatives() {
void UIConstantsProviderManager::installJSIBindings() {
runtimeExecutor_([thizz = this](jsi::Runtime& runtime) {
auto uiConstantsProvider = [thizz, &runtime]() -> jsi::Value {
static auto getConstants =
jni::findClassStatic(
UIConstantsProviderManager::UIConstantsProviderJavaDescriptor)
->getMethod<jni::alias_ref<NativeMap::jhybridobject>()>(
"getConstants");
auto constants = getConstants(thizz->uiConstantsProvider_.get());
return jsi::valueFromDynamic(runtime, constants->cthis()->consume());
auto jsiDefaultEventTypesProvider = [thizz, &runtime]() -> jsi::Value {
return thizz->defaultExportableEventTypesProvider_->getDefaultEventTypes(
runtime);
};
auto jsiConstantsForViewManagerProvider =
[thizz, &runtime](std::string viewManagerName) -> jsi::Value {
return thizz->constantsForViewManagerProvider_
->getConstantsForViewManager(runtime, viewManagerName);
};
auto jsiConstantsProvider = [thizz, &runtime]() -> jsi::Value {
return thizz->constantsProvider_->getConstants(runtime);
};
LegacyUIManagerConstantsProviderBinding::install(
runtime, std::move(uiConstantsProvider));
runtime,
"getDefaultEventTypes",
std::move(jsiDefaultEventTypesProvider));
LegacyUIManagerConstantsProviderBinding::install(
runtime,
"getConstantsForViewManager",
std::move(jsiConstantsForViewManagerProvider));
LegacyUIManagerConstantsProviderBinding::install(
runtime, "getConstants", std::move(jsiConstantsProvider));
});
}
@@ -9,23 +9,80 @@
#include <ReactCommon/RuntimeExecutor.h>
#include <fbjni/fbjni.h>
#include <jsi/JSIDynamic.h>
#include <jsi/jsi.h>
#include <react/jni/JRuntimeExecutor.h>
#include <react/jni/NativeMap.h>
namespace facebook::react {
class DefaultEventTypesProvider
: public jni::JavaClass<DefaultEventTypesProvider> {
public:
static constexpr const char* kJavaDescriptor =
"Lcom/facebook/react/uimanager/UIConstantsProviderManager$DefaultEventTypesProvider;";
jsi::Value getDefaultEventTypes(jsi::Runtime& runtime) {
static auto method =
javaClassStatic()
->getMethod<jni::alias_ref<NativeMap::jhybridobject>()>(
"getDefaultEventTypes");
auto result = method(self());
return jsi::valueFromDynamic(runtime, result->cthis()->consume());
}
};
class ConstantsForViewManagerProvider
: public jni::JavaClass<ConstantsForViewManagerProvider> {
public:
static constexpr const char* kJavaDescriptor =
"Lcom/facebook/react/uimanager/UIConstantsProviderManager$ConstantsForViewManagerProvider;";
jsi::Value getConstantsForViewManager(
jsi::Runtime& runtime,
std::string viewManagerName) {
static auto method =
javaClassStatic()
->getMethod<jni::alias_ref<NativeMap::jhybridobject>(std::string)>(
"getConstantsForViewManager");
auto result = method(self(), viewManagerName);
if (result == nullptr) {
return jsi::Value::null();
}
return jsi::valueFromDynamic(runtime, result->cthis()->consume());
}
};
class ConstantsProvider : public jni::JavaClass<ConstantsProvider> {
public:
static constexpr const char* kJavaDescriptor =
"Lcom/facebook/react/uimanager/UIConstantsProviderManager$ConstantsProvider;";
jsi::Value getConstants(jsi::Runtime& runtime) {
static auto method =
javaClassStatic()
->getMethod<jni::alias_ref<NativeMap::jhybridobject>()>(
"getConstants");
auto result = method(self());
return jsi::valueFromDynamic(runtime, result->cthis()->consume());
}
};
class UIConstantsProviderManager
: public facebook::jni::HybridClass<UIConstantsProviderManager> {
public:
static auto constexpr kJavaDescriptor =
"Lcom/facebook/react/uimanager/UIConstantsProviderManager;";
constexpr static auto UIConstantsProviderJavaDescriptor =
"com/facebook/react/uimanager/UIConstantsProvider";
static facebook::jni::local_ref<jhybriddata> initHybrid(
facebook::jni::alias_ref<jhybridobject> jThis,
facebook::jni::alias_ref<JRuntimeExecutor::javaobject> runtimeExecutor,
facebook::jni::alias_ref<jobject> uiConstantsProviderManager);
facebook::jni::alias_ref<DefaultEventTypesProvider::javaobject>
defaultExportableEventTypesProvider,
facebook::jni::alias_ref<ConstantsForViewManagerProvider::javaobject>
constantsForViewManagerProvider,
facebook::jni::alias_ref<ConstantsProvider::javaobject>
constantsProvider);
static void registerNatives();
@@ -34,14 +91,23 @@ class UIConstantsProviderManager
facebook::jni::global_ref<UIConstantsProviderManager::javaobject> javaPart_;
RuntimeExecutor runtimeExecutor_;
facebook::jni::global_ref<jobject> uiConstantsProvider_;
facebook::jni::global_ref<DefaultEventTypesProvider::javaobject>
defaultExportableEventTypesProvider_;
facebook::jni::global_ref<ConstantsForViewManagerProvider::javaobject>
constantsForViewManagerProvider_;
facebook::jni::global_ref<ConstantsProvider::javaobject> constantsProvider_;
void installJSIBindings();
explicit UIConstantsProviderManager(
facebook::jni::alias_ref<UIConstantsProviderManager::jhybridobject> jThis,
RuntimeExecutor runtimeExecutor,
facebook::jni::alias_ref<jobject> uiConstantsProviderManager);
facebook::jni::alias_ref<DefaultEventTypesProvider::javaobject>
defaultExportableEventTypesProvider,
facebook::jni::alias_ref<ConstantsForViewManagerProvider::javaobject>
constantsForViewManagerProvider,
facebook::jni::alias_ref<ConstantsProvider::javaobject>
constantsProvider);
};
} // namespace facebook::react
@@ -9,8 +9,11 @@
namespace facebook::react::LegacyUIManagerConstantsProviderBinding {
void install(jsi::Runtime& runtime, ProviderType&& provider) {
auto name = "RN$LegacyInterop_UIManager_getConstants";
void install(
jsi::Runtime& runtime,
const std::string& name,
std::function<jsi::Value()>&& provider) {
auto methodName = "RN$LegacyInterop_UIManager_" + name;
auto hostFunction = [provider = std::move(provider)](
jsi::Runtime& runtime,
const jsi::Value& /*thisValue*/,
@@ -25,6 +28,34 @@ void install(jsi::Runtime& runtime, ProviderType&& provider) {
auto jsiFunction = jsi::Function::createFromHostFunction(
runtime, jsi::PropNameID::forAscii(runtime, name), 2, hostFunction);
runtime.global().setProperty(runtime, name, jsiFunction);
runtime.global().setProperty(runtime, methodName.c_str(), jsiFunction);
}
void install(
jsi::Runtime& runtime,
const std::string& name,
std::function<jsi::Value(std::string)>&& provider) {
auto methodName = "RN$LegacyInterop_UIManager_" + name;
auto hostFunction = [provider = std::move(provider)](
jsi::Runtime& runtime,
const jsi::Value& /*thisValue*/,
const jsi::Value* args,
size_t count) -> jsi::Value {
if (count != 1) {
throw new jsi::JSError(runtime, "1 argument expected.");
}
if (!args[0].isString()) {
throw new jsi::JSError(runtime, "First argument must be string.");
}
return provider(args[0].asString(runtime).utf8(runtime));
};
auto jsiFunction = jsi::Function::createFromHostFunction(
runtime, jsi::PropNameID::forAscii(runtime, name), 2, hostFunction);
runtime.global().setProperty(runtime, methodName.c_str(), jsiFunction);
}
} // namespace facebook::react::LegacyUIManagerConstantsProviderBinding
@@ -18,5 +18,12 @@ using ProviderType = std::function<jsi::Value()>;
* runtime. It is supposed to be used as a substitute to UIManager.getConstants
* in bridgeless mode.
*/
void install(jsi::Runtime& runtime, ProviderType&& provider);
void install(
jsi::Runtime& runtime,
const std::string& name,
std::function<jsi::Value()>&& provider);
void install(
jsi::Runtime& runtime,
const std::string& name,
std::function<jsi::Value(std::string)>&& provider);
} // namespace facebook::react::LegacyUIManagerConstantsProviderBinding
@@ -39,6 +39,6 @@ jsi::Value getConstants(facebook::jsi::Runtime &runtime)
void installLegacyUIManagerConstantsProviderBinding(jsi::Runtime &runtime)
{
auto constantsProvider = [&runtime]() -> jsi::Value { return getConstants(runtime); };
LegacyUIManagerConstantsProviderBinding::install(runtime, std::move(constantsProvider));
LegacyUIManagerConstantsProviderBinding::install(runtime, "getConstants", std::move(constantsProvider));
}
} // namespace facebook::react