diff --git a/packages/react-native/ReactAndroid/build.gradle.kts b/packages/react-native/ReactAndroid/build.gradle.kts index 7bbb4db28983..471e90553e98 100644 --- a/packages/react-native/ReactAndroid/build.gradle.kts +++ b/packages/react-native/ReactAndroid/build.gradle.kts @@ -655,6 +655,7 @@ tasks.withType().configureEach { } dependencies { + implementation("com.tencent:mmkv-static:1.2.14") api(libs.androidx.appcompat) api(libs.androidx.appcompat.resources) api(libs.androidx.autofill) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java index 031f534afec3..612b252c8898 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java @@ -19,6 +19,7 @@ import com.facebook.react.modules.core.PermissionListener; import com.facebook.react.util.AndroidVersion; import org.jetbrains.annotations.NotNull; +import com.facebook.react.uimanager.UIManagerConstantsCache; /** Base Activity for React Native applications. */ public abstract class ReactActivity extends AppCompatActivity @@ -58,6 +59,7 @@ protected ReactActivityDelegate createReactActivityDelegate() { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + UIManagerConstantsCache.getInstance().init(this); mDelegate.onCreate(savedInstanceState); if (AndroidVersion.isAtLeastTargetSdk36(this)) { getOnBackPressedDispatcher().addCallback(this, mBackPressedCallback); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaModuleWrapper.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaModuleWrapper.java new file mode 100644 index 000000000000..faae687d82b4 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaModuleWrapper.java @@ -0,0 +1,158 @@ +/* + * 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.bridge; + +import static com.facebook.react.bridge.ReactMarkerConstants.CONVERT_CONSTANTS_END; +import static com.facebook.react.bridge.ReactMarkerConstants.CONVERT_CONSTANTS_START; +import static com.facebook.react.bridge.ReactMarkerConstants.GET_CONSTANTS_END; +import static com.facebook.react.bridge.ReactMarkerConstants.GET_CONSTANTS_START; +import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE; + +import androidx.annotation.Nullable; +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.react.turbomodule.core.interfaces.TurboModule; +import com.facebook.systrace.Systrace; +import com.facebook.systrace.SystraceMessage; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import com.facebook.react.uimanager.UIManagerConstantsCache; + +/** + * This is part of the glue which wraps a java BaseJavaModule in a C++ NativeModule. This could all + * be in C++, but it's android-specific initialization code, and writing it this way is easier to + * read and means fewer JNI calls. + */ +@DoNotStrip +class JavaModuleWrapper { + + interface NativeMethod { + void invoke(JSInstance jsInstance, ReadableArray parameters); + + String getType(); + } + + @DoNotStrip + public static class MethodDescriptor { + @DoNotStrip Method method; + @DoNotStrip String signature; + @DoNotStrip String name; + @DoNotStrip String type; + } + + private final JSInstance mJSInstance; + private final ModuleHolder mModuleHolder; + private final ArrayList mMethods; + private final ArrayList mDescs; + + public JavaModuleWrapper(JSInstance jsInstance, ModuleHolder moduleHolder) { + mJSInstance = jsInstance; + mModuleHolder = moduleHolder; + mMethods = new ArrayList<>(); + mDescs = new ArrayList<>(); + } + + @DoNotStrip + public BaseJavaModule getModule() { + return (BaseJavaModule) mModuleHolder.getModule(); + } + + @DoNotStrip + public String getName() { + return mModuleHolder.getName(); + } + + @DoNotStrip + private void findMethods() { + Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "findMethods"); + + Class classForMethods = mModuleHolder.getModule().getClass(); + Class superClass = + (Class) classForMethods.getSuperclass(); + if (TurboModule.class.isAssignableFrom(superClass)) { + // For java module that is based on generated flow-type spec, inspect the + // spec abstract class instead, which is the super class of the given java + // module. + classForMethods = superClass; + } + Method[] targetMethods = classForMethods.getDeclaredMethods(); + + for (Method targetMethod : targetMethods) { + ReactMethod annotation = targetMethod.getAnnotation(ReactMethod.class); + if (annotation != null) { + String methodName = targetMethod.getName(); + MethodDescriptor md = new MethodDescriptor(); + JavaMethodWrapper method = + new JavaMethodWrapper(this, targetMethod, annotation.isBlockingSynchronousMethod()); + md.name = methodName; + md.type = method.getType(); + if (BaseJavaModule.METHOD_TYPE_SYNC.equals(md.type)) { + md.signature = method.getSignature(); + md.method = targetMethod; + } + mMethods.add(method); + mDescs.add(md); + } + } + Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE); + } + + @DoNotStrip + public List getMethodDescriptors() { + if (mDescs.isEmpty()) { + findMethods(); + } + return mDescs; + } + + @DoNotStrip + public @Nullable NativeMap getConstants() { + final String moduleName = getName(); + SystraceMessage.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "JavaModuleWrapper.getConstants") + .arg("moduleName", moduleName) + .flush(); + ReactMarker.logMarker(GET_CONSTANTS_START, moduleName); + + + Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "module.getModule"); + BaseJavaModule baseJavaModule = getModule(); + Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE); + + Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "module.getConstants"); + Map map = baseJavaModule.getConstants(); + Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE); + + Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "create WritableNativeMap"); + ReactMarker.logMarker(CONVERT_CONSTANTS_START, moduleName); + try { + if (moduleName == "UIManager") { + NativeMap res = UIManagerConstantsCache.getInstance().getUIManagerConstantsAsWritableMap(); + if (res != null) { + return res; + } + } + return Arguments.makeNativeMap(map); + } finally { + ReactMarker.logMarker(CONVERT_CONSTANTS_END, moduleName); + Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE); + + ReactMarker.logMarker(GET_CONSTANTS_END, moduleName); + SystraceMessage.endSection(TRACE_TAG_REACT_JAVA_BRIDGE).flush(); + } + } + + @DoNotStrip + public void invoke(int methodId, ReadableNativeArray parameters) { + if (methodId >= mMethods.size()) { + return; + } + + mMethods.get(methodId).invoke(mJSInstance, parameters); + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaModuleWrapper.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaModuleWrapper.kt deleted file mode 100644 index 40b0b9cbb96a..000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaModuleWrapper.kt +++ /dev/null @@ -1,142 +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.bridge - -import com.facebook.proguard.annotations.DoNotStrip -import com.facebook.react.common.annotations.internal.InteropLegacyArchitecture -import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel -import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger.assertLegacyArchitecture -import com.facebook.react.turbomodule.core.interfaces.TurboModule -import com.facebook.systrace.Systrace -import com.facebook.systrace.Systrace.TRACE_TAG_REACT -import com.facebook.systrace.SystraceMessage -import java.lang.reflect.Method - -/** - * This is part of the glue which wraps a java BaseJavaModule in a C++ NativeModule. This could all - * be in C++, but it's android-specific initialization code, and writing it this way is easier to - * read and means fewer JNI calls. - */ -@DoNotStrip -@InteropLegacyArchitecture -internal class JavaModuleWrapper( - private val jsInstance: JSInstance, - private val moduleHolder: ModuleHolder -) { - internal interface NativeMethod { - fun invoke(jsInstance: JSInstance, parameters: ReadableArray) - - val type: String - } - - @DoNotStrip - class MethodDescriptor { - @DoNotStrip var method: Method? = null - - @DoNotStrip var signature: String? = null - - @DoNotStrip var name: String? = null - - @DoNotStrip var type: String? = null - } - - private val methods = ArrayList() - private val descs = ArrayList() - - @get:DoNotStrip - val module: BaseJavaModule - get() = moduleHolder.module as BaseJavaModule - - @get:DoNotStrip - val name: String - get() = moduleHolder.name - - @DoNotStrip - private fun findMethods() { - Systrace.beginSection(TRACE_TAG_REACT, "findMethods") - - var classForMethods: Class<*> = moduleHolder.module.javaClass - val superClass = classForMethods.superclass - if (superClass != null && TurboModule::class.java.isAssignableFrom(superClass)) { - // For java module that is based on generated flow-type spec, inspect the - // spec abstract class instead, which is the super class of the given Java - // module. - classForMethods = superClass - } - - val targetMethods = classForMethods.declaredMethods - for (targetMethod in targetMethods) { - targetMethod.getAnnotation(ReactMethod::class.java)?.let { annotation -> - val methodName = targetMethod.name - val md = MethodDescriptor() - val method = JavaMethodWrapper(this, targetMethod, annotation.isBlockingSynchronousMethod) - md.name = methodName - md.type = method.type - if (BaseJavaModule.METHOD_TYPE_SYNC == md.type) { - md.signature = method.signature - md.method = targetMethod - } - methods.add(method) - descs.add(md) - } - } - Systrace.endSection(TRACE_TAG_REACT) - } - - @get:DoNotStrip - val methodDescriptors: List - get() { - if (descs.isEmpty()) { - findMethods() - } - return descs - } - - @get:DoNotStrip - val constants: NativeMap - get() { - val moduleName = name - SystraceMessage.beginSection(TRACE_TAG_REACT, "JavaModuleWrapper.getConstants") - .arg("moduleName", moduleName) - .flush() - ReactMarker.logMarker(ReactMarkerConstants.GET_CONSTANTS_START, moduleName) - - val baseJavaModule = module - - Systrace.beginSection(TRACE_TAG_REACT, "module.getConstants") - val map = baseJavaModule.constants - Systrace.endSection(TRACE_TAG_REACT) - - Systrace.beginSection(TRACE_TAG_REACT, "create WritableNativeMap") - ReactMarker.logMarker(ReactMarkerConstants.CONVERT_CONSTANTS_START, moduleName) - try { - return Arguments.makeNativeMap(map) - } finally { - ReactMarker.logMarker(ReactMarkerConstants.CONVERT_CONSTANTS_END, moduleName) - Systrace.endSection(TRACE_TAG_REACT) - - ReactMarker.logMarker(ReactMarkerConstants.GET_CONSTANTS_END, moduleName) - SystraceMessage.endSection(TRACE_TAG_REACT).flush() - } - } - - @DoNotStrip - fun invoke(methodId: Int, parameters: ReadableNativeArray) { - if (methodId >= methods.size) { - return - } - - methods[methodId].invoke(jsInstance, parameters) - } - - companion object { - init { - assertLegacyArchitecture("JavaModuleWrapper", LegacyArchitectureLogLevel.WARNING) - } - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerConstantsCache.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerConstantsCache.java new file mode 100644 index 000000000000..f631ed716334 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerConstantsCache.java @@ -0,0 +1,352 @@ +package com.facebook.react.uimanager; + +import android.content.Context; +import android.util.Log; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.WritableNativeMap; +import com.tencent.mmkv.MMKV; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; + +/** + * Caches two separate JSON blobs in MMKV: + * • Full UIManager constants (under MMKV_KEY_CONSTANTS) + * • bubblingEventTypes map only (under MMKV_KEY_BUBBLING) + * + * On init(...), a background thread: + * 1) calls MMKV.initialize(...) + * 2) reads both keys, parses them into Maps + * 3) builds a WritableNativeMap from the full constants map via Arguments.makeNativeMap(...) + * 4) countDown()s loadLatch + * + * Exposed methods all block until that single background load finishes: + * • getCachedConstants() → Map or null + * • getCachedBubblingEventsTypes() → Map or null + * • getUIManagerConstantsAsWritableMap() → WritableNativeMap or null + * + * To persist: + * • call saveConstantsAndBubblingEventsTypes(freshConstantsMap, freshBubblingEventsMap) + * which writes two JSON strings (one under each key) off the main thread, + * and updates in‐memory maps immediately. + */ +public class UIManagerConstantsCache { + private static final String TAG = "UIManagerConstantsCache"; + + // MMKV keys for two separate blobs + private static final String MMKV_KEY_CONSTANTS = "UIManagerModuleConstants_v1"; + private static final String MMKV_KEY_BUBBLING = "UIManagerModuleBubbling_v1"; + + private static final UIManagerConstantsCache INSTANCE = new UIManagerConstantsCache(); + + /** In-memory store of the parsed Map (full constants). */ + private Map cachedConstants = null; + + /** In-memory store of the parsed Map (bubblingEventTypes only). */ + private Map cachedBubblingEventsTypes = null; + + /** In-memory store of the pre-built WritableNativeMap for full constants. */ + private WritableNativeMap cachedNativeMap = null; + + /** Latch that background-loads both JSON blobs exactly once. */ + private final CountDownLatch loadLatch = new CountDownLatch(1); + + /** Ensures init(...) is only done once. */ + private volatile boolean initCalled = false; + + private UIManagerConstantsCache() { + // private constructor + } + + public static UIManagerConstantsCache getInstance() { + return INSTANCE; + } + + /** + * Must be called (once) from Application or MainActivity before any UIManager + * constants are accessed. This kicks off: + * 1) MMKV.initialize(...) + * 2) A background thread that reads two MMKV keys, parses them → Maps, + * then builds a WritableNativeMap from the full constants, and finally + * countDown()s loadLatch. + */ + public synchronized void init(Context appContext) { + if (initCalled) { + return; + } + initCalled = true; + + // 1) Initialize MMKV + MMKV.initialize(appContext.getApplicationContext()); + + // 2) Background thread to load both blobs + new Thread(() -> { + try { + MMKV mmkv = MMKV.defaultMMKV(); + + // 2a) Read full-constants JSON + String jsonConstants = mmkv.decodeString(MMKV_KEY_CONSTANTS, null); + if (jsonConstants != null) { + try { + JSONObject rootConsts = new JSONObject(jsonConstants); + Map mapConsts = jsonToMap(rootConsts); + synchronized (this) { + cachedConstants = mapConsts; + } + Log.v(TAG, "Background-loaded full UIManager constants (size=" + + (mapConsts == null ? 0 : mapConsts.size()) + ")"); + } catch (JSONException je) { + Log.w(TAG, "Invalid JSON in MMKV (constants). Will regenerate.\n" + + jsonConstants, je); + synchronized (this) { + cachedConstants = null; + } + } + } else { + synchronized (this) { + cachedConstants = null; + } + Log.v(TAG, "No UIManager constants found in MMKV."); + } + + // 2b) Read bubblingEventTypes JSON + String jsonBubbling = mmkv.decodeString(MMKV_KEY_BUBBLING, null); + if (jsonBubbling != null) { + try { + JSONObject rootBub = new JSONObject(jsonBubbling); + Map mapBub = jsonToMap(rootBub); + synchronized (this) { + cachedBubblingEventsTypes = mapBub; + } + Log.v(TAG, "Background-loaded bubblingEventTypes (size=" + + (mapBub == null ? 0 : mapBub.size()) + ")"); + } catch (JSONException je) { + Log.w(TAG, "Invalid JSON in MMKV (bubblingEventTypes). Will regenerate.\n" + + jsonBubbling, je); + synchronized (this) { + cachedBubblingEventsTypes = null; + } + } + } else { + synchronized (this) { + cachedBubblingEventsTypes = null; + } + Log.v(TAG, "No bubblingEventTypes found in MMKV."); + } + + // 2c) Build WritableNativeMap from full constants (if available) + synchronized (this) { + if (cachedConstants != null) { + cachedNativeMap = Arguments.makeNativeMap(cachedConstants); + } else { + cachedNativeMap = null; + } + } + } finally { + // Signal load completion + loadLatch.countDown(); + } + }, "UIManagerConstantsCache-Loader").start(); + } + + /** + * Blocks until background-load finishes, then returns the full constants map. + * @return parsed Map or null if none stored / parse failed. + */ + public Map getCachedConstants() { + try { + loadLatch.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + Log.w(TAG, "getCachedConstants() interrupted while waiting."); + return null; + } + synchronized (this) { + return cachedConstants; + } + } + + /** + * Blocks until background-load finishes, then returns the bubblingEventTypes map. + * @return parsed Map or null if none stored / parse failed. + */ + public Map getCachedBubblingEventsTypes() { + try { + loadLatch.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + Log.w(TAG, "getCachedBubblingEventsTypes() interrupted while waiting."); + return null; + } + synchronized (this) { + return cachedBubblingEventsTypes; + } + } + + /** + * Blocks until background-load (and WritableNativeMap build) finishes. + * @return pre-built WritableNativeMap or null if no full-constants available. + */ + public WritableNativeMap getUIManagerConstantsAsWritableMap() { + try { + loadLatch.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + Log.w(TAG, "getUIManagerConstantsAsWritableMap() interrupted while waiting."); + return null; + } + synchronized (this) { + return cachedNativeMap; + } + } + + /** + * Takes freshly-built maps (full constants & bubblingEventTypes) and: + * 1) Spawns a background thread to JSON-serialize + write each to its own MMKV key, + * 2) Updates in-memory caches (constants, bubbling types, and native map) so getters return immediately. + */ + public void saveConstantsAndBubblingEventsTypes( + final Map constants, + final Map bubblingEventsTypes) { + if (constants == null) { + return; + } + + // Spawn a background thread to write both JSON blobs + new Thread(() -> { + try { + // Serialize full constants + JSONObject jsonConsts = mapToJson(constants); + long t0 = System.currentTimeMillis(); + MMKV.defaultMMKV().encode(MMKV_KEY_CONSTANTS, jsonConsts.toString()); + long t1 = System.currentTimeMillis(); + Log.v(TAG, "Saved UIManager constants to MMKV in " + (t1 - t0) + "ms"); + } catch (JSONException e) { + Log.e(TAG, "Failed to JSON-serialize UIManager constants; not caching.", e); + } + + if (bubblingEventsTypes != null) { + try { + // Serialize bubblingEventTypes + JSONObject jsonBub = mapToJson(bubblingEventsTypes); + long t2 = System.currentTimeMillis(); + MMKV.defaultMMKV().encode(MMKV_KEY_BUBBLING, jsonBub.toString()); + long t3 = System.currentTimeMillis(); + Log.v(TAG, "Saved bubblingEventTypes to MMKV in " + (t3 - t2) + "ms"); + } catch (JSONException e) { + Log.e(TAG, "Failed to JSON-serialize bubblingEventTypes; not caching.", e); + } + } + }, "UIManagerConstantsCache-Saver").start(); + } + + // ──────────────────────────────────────────────────────────────────────────────── + // JSON ↔ Map Helpers (identical to before) + // ──────────────────────────────────────────────────────────────────────────────── + + private static JSONObject mapToJson(Map map) throws JSONException { + JSONObject json = new JSONObject(); + for (Map.Entry entry : map.entrySet()) { + String key = entry.getKey(); + Object val = entry.getValue(); + if (val == null) { + json.put(key, JSONObject.NULL); + } else if (val instanceof String) { + json.put(key, (String) val); + } else if (val instanceof Boolean) { + json.put(key, (Boolean) val); + } else if (val instanceof Number) { + json.put(key, (Number) val); + } else if (val instanceof Map) { + //noinspection unchecked + json.put(key, mapToJson((Map) val)); + } else if (val instanceof List) { + //noinspection unchecked + json.put(key, listToJsonArray((List) val)); + } else { + throw new JSONException( + "Unsupported value type for key \"" + key + "\": " + val.getClass() + ); + } + } + return json; + } + + private static JSONArray listToJsonArray(List list) throws JSONException { + JSONArray arr = new JSONArray(); + for (Object elem : list) { + if (elem == null) { + arr.put(JSONObject.NULL); + } else if (elem instanceof String) { + arr.put((String) elem); + } else if (elem instanceof Boolean) { + arr.put((Boolean) elem); + } else if (elem instanceof Number) { + arr.put((Number) elem); + } else if (elem instanceof Map) { + //noinspection unchecked + arr.put(mapToJson((Map) elem)); + } else if (elem instanceof List) { + //noinspection unchecked + arr.put(listToJsonArray((List) elem)); + } else { + throw new JSONException("Unsupported list element type: " + elem.getClass()); + } + } + return arr; + } + + private static Map jsonToMap(JSONObject json) throws JSONException { + Map result = new HashMap<>(); + Iterator keys = json.keys(); + while (keys.hasNext()) { + String key = keys.next(); + Object raw = json.get(key); + if (raw == JSONObject.NULL) { + result.put(key, null); + } else if (raw instanceof Boolean || raw instanceof Number || raw instanceof String) { + result.put(key, raw); + } else if (raw instanceof JSONObject) { + result.put(key, jsonToMap((JSONObject) raw)); + } else if (raw instanceof JSONArray) { + result.put(key, jsonArrayToList((JSONArray) raw)); + } else { + throw new JSONException( + "Unsupported JSON type in UIManager constants for key \"" + key + "\": " + + raw.getClass() + ); + } + } + return result; + } + + private static List jsonArrayToList(JSONArray arr) throws JSONException { + List result = new ArrayList<>(); + for (int i = 0; i < arr.length(); i++) { + Object raw = arr.get(i); + if (raw == JSONObject.NULL) { + result.add(null); + } else if (raw instanceof Boolean || raw instanceof Number || raw instanceof String) { + result.add(raw); + } else if (raw instanceof JSONObject) { + result.add(jsonToMap((JSONObject) raw)); + } else if (raw instanceof JSONArray) { + result.add(jsonArrayToList((JSONArray) raw)); + } else { + throw new JSONException( + "Unsupported JSON array element at index " + i + ": " + raw.getClass() + ); + } + } + return result; + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index 68d99e4b93cf..376845135560 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -9,9 +9,10 @@ import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_UI_MANAGER_MODULE_CONSTANTS_END; import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_UI_MANAGER_MODULE_CONSTANTS_START; +import static com.facebook.react.uimanager.common.UIManagerType.DEFAULT; import static com.facebook.react.uimanager.common.UIManagerType.FABRIC; -import static com.facebook.react.uimanager.common.UIManagerType.LEGACY; +import android.util.Log; import android.content.ComponentCallbacks2; import android.content.res.Configuration; import android.view.View; @@ -39,17 +40,12 @@ import com.facebook.react.bridge.WritableMap; import com.facebook.react.common.MapBuilder; import com.facebook.react.common.ReactConstants; -import com.facebook.react.common.annotations.internal.LegacyArchitecture; -import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel; -import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger; -import com.facebook.react.common.build.ReactBuildConfig; import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.uimanager.common.ViewUtil; import com.facebook.react.uimanager.debug.NotThreadSafeViewHierarchyUpdateDebugListener; import com.facebook.react.uimanager.events.EventDispatcher; import com.facebook.react.uimanager.events.EventDispatcherImpl; import com.facebook.react.uimanager.events.RCTEventEmitter; -import com.facebook.react.uimanager.internal.LegacyArchitectureShadowNodeLogger; import com.facebook.systrace.Systrace; import com.facebook.systrace.SystraceMessage; import java.util.ArrayList; @@ -87,14 +83,8 @@ * Don't dispatch the view hierarchy at the end of a batch if no UI changes occurred */ @ReactModule(name = UIManagerModule.NAME) -@LegacyArchitecture public class UIManagerModule extends ReactContextBaseJavaModule implements OnBatchCompleteListener, LifecycleEventListener, UIManager { - static { - LegacyArchitectureLogger.assertLegacyArchitecture( - "UIManagerModule", LegacyArchitectureLogLevel.WARNING); - } - public static final String TAG = UIManagerModule.class.getSimpleName(); /** Resolves a name coming from native side to a name of the event that is exposed to JS. */ @@ -129,7 +119,7 @@ public UIManagerModule( DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(reactContext); mEventDispatcher = new EventDispatcherImpl(reactContext); mModuleConstants = createConstants(viewManagerResolver); - mCustomDirectEvents = UIManagerModuleConstants.directEventTypeConstants; + mCustomDirectEvents = UIManagerModuleConstants.getDirectEventTypeConstants(); mViewManagerRegistry = new ViewManagerRegistry(viewManagerResolver); mUIImplementation = new UIImplementation( @@ -146,26 +136,44 @@ public UIManagerModule( List viewManagersList, int minTimeLeftInFrameForNonBatchedOperationMs) { super(reactContext); + Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "UIManagerModule.init"); DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(reactContext); + long startTime, endTime; + + startTime = System.currentTimeMillis(); mEventDispatcher = new EventDispatcherImpl(reactContext); + endTime = System.currentTimeMillis(); + Log.v(ReactConstants.TAG, "UIManagerModule: EventDispatcherImpl init took " + (endTime - startTime) + " ms"); + + startTime = System.currentTimeMillis(); mCustomDirectEvents = MapBuilder.newHashMap(); + endTime = System.currentTimeMillis(); + Log.v(ReactConstants.TAG, "UIManagerModule: CustomDirectEvents init took " + (endTime - startTime) + " ms"); + + startTime = System.currentTimeMillis(); mModuleConstants = createConstants(viewManagersList, null, mCustomDirectEvents); + endTime = System.currentTimeMillis(); + Log.v(ReactConstants.TAG, "UIManagerModule: ModuleConstants init took " + (endTime - startTime) + " ms"); + + startTime = System.currentTimeMillis(); mViewManagerRegistry = new ViewManagerRegistry(viewManagersList); - mUIImplementation = - new UIImplementation( - reactContext, - mViewManagerRegistry, - mEventDispatcher, - minTimeLeftInFrameForNonBatchedOperationMs); + endTime = System.currentTimeMillis(); + Log.v(ReactConstants.TAG, "UIManagerModule: ViewManagerRegistry init took " + (endTime - startTime) + " ms"); - if (ReactBuildConfig.DEBUG) { - for (ViewManager viewManager : viewManagersList) { - LegacyArchitectureShadowNodeLogger.assertUnsupportedViewManager( - reactContext, viewManager.getShadowNodeClass(), viewManager.getClass().getSimpleName()); - } - } + startTime = System.currentTimeMillis(); + mUIImplementation = + new UIImplementation( + reactContext, + mViewManagerRegistry, + mEventDispatcher, + minTimeLeftInFrameForNonBatchedOperationMs); + endTime = System.currentTimeMillis(); + Log.v(ReactConstants.TAG, "UIManagerModule: UIImplementation init took " + (endTime - startTime) + " ms"); reactContext.addLifecycleEventListener(this); + mEventDispatcher.registerEventEmitter( + DEFAULT, getReactApplicationContext().getJSModule(RCTEventEmitter.class)); + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } /** @@ -244,13 +252,13 @@ public ViewManagerRegistry getViewManagerRegistry_DO_NOT_USE() { private static Map createConstants(ViewManagerResolver viewManagerResolver) { ReactMarker.logMarker(CREATE_UI_MANAGER_MODULE_CONSTANTS_START); - SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT, "CreateUIManagerConstants") + SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "CreateUIManagerConstants") .arg("Lazy", true) .flush(); try { return UIManagerModuleConstantsHelper.createConstants(viewManagerResolver); } finally { - Systrace.endSection(Systrace.TRACE_TAG_REACT); + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); ReactMarker.logMarker(CREATE_UI_MANAGER_MODULE_CONSTANTS_END); } } @@ -260,14 +268,30 @@ public static Map createConstants( @Nullable Map customBubblingEvents, @Nullable Map customDirectEvents) { ReactMarker.logMarker(CREATE_UI_MANAGER_MODULE_CONSTANTS_START); - SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT, "CreateUIManagerConstants") + SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "CreateUIManagerConstants") .arg("Lazy", false) .flush(); try { - return UIManagerModuleConstantsHelper.createConstants( + // If the background load is still happening, this will wait. If it completed, returns immediately. + Map cached = UIManagerConstantsCache.getInstance().getCachedConstants(); + Map cachedCustomBubblingEvents = UIManagerConstantsCache.getInstance().getCachedBubblingEventsTypes(); + if (cached != null) { + if (customBubblingEvents != null) { + customBubblingEvents.putAll(cachedCustomBubblingEvents); + } + + return cached; + } + + // Otherwise, build fresh on this thread… + Map fresh = UIManagerModuleConstantsHelper.createConstants( viewManagers, customBubblingEvents, customDirectEvents); + + // …and save to MMKV in the background for next time: + UIManagerConstantsCache.getInstance().saveConstantsAndBubblingEventsTypes(fresh, customBubblingEvents); + return fresh; } finally { - Systrace.endSection(Systrace.TRACE_TAG_REACT); + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); ReactMarker.logMarker(CREATE_UI_MANAGER_MODULE_CONSTANTS_END); } } @@ -285,7 +309,7 @@ public static Map createConstants( public static @Nullable WritableMap getConstantsForViewManager( ViewManager viewManager, Map customDirectEvents) { SystraceMessage.beginSection( - Systrace.TRACE_TAG_REACT, "UIManagerModule.getConstantsForViewManager") + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "UIManagerModule.getConstantsForViewManager") .arg("ViewManager", viewManager.getName()) .arg("Lazy", true) .flush(); @@ -298,7 +322,7 @@ public static Map createConstants( } return null; } finally { - SystraceMessage.endSection(Systrace.TRACE_TAG_REACT).flush(); + SystraceMessage.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE).flush(); } } @@ -372,7 +396,7 @@ public void synchronouslyUpdateViewOnUIThread(int tag, ReadableMap props) { */ @Override public int addRootView(final T rootView, WritableMap initialProps) { - Systrace.beginSection(Systrace.TRACE_TAG_REACT, "UIManagerModule.addRootView"); + Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "UIManagerModule.addRootView"); final int tag = ReactRootViewTagGenerator.getNextRootViewTag(); final ReactApplicationContext reactApplicationContext = getReactApplicationContext(); @@ -385,7 +409,7 @@ public int addRootView(final T rootView, WritableMap initialPro -1); mUIImplementation.registerRootView(rootView, tag, themedRootContext); - Systrace.endSection(Systrace.TRACE_TAG_REACT); + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); return tag; } @@ -682,7 +706,7 @@ public void onBatchComplete() { int batchId = mBatchId; mBatchId++; - SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT, "onBatchCompleteUI") + SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "onBatchCompleteUI") .arg("BatchId", batchId) .flush(); for (UIManagerModuleListener listener : mListeners) { @@ -699,7 +723,7 @@ public void onBatchComplete() { mUIImplementation.dispatchViewUpdates(batchId); } } finally { - Systrace.endSection(Systrace.TRACE_TAG_REACT); + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } } @@ -858,7 +882,7 @@ public void receiveEvent(int reactTag, String eventName, @Nullable WritableMap e @Override public void receiveEvent( int surfaceId, int reactTag, String eventName, @Nullable WritableMap event) { - assert ViewUtil.getUIManagerType(reactTag) == LEGACY; + assert ViewUtil.getUIManagerType(reactTag) == DEFAULT; getReactApplicationContext() .getJSModule(RCTEventEmitter.class) .receiveEvent(reactTag, eventName, event); diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/JavaModuleWrapper.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/jni/JavaModuleWrapper.cpp index 65a76fc7618b..03985173b672 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/JavaModuleWrapper.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/JavaModuleWrapper.cpp @@ -15,6 +15,8 @@ #include #include #include +#include +#include #ifdef WITH_FBSYSTRACE #include @@ -50,16 +52,19 @@ std::string JavaNativeModule::getName() { std::string JavaNativeModule::getSyncMethodName(unsigned int reactMethodId) { if (reactMethodId >= syncMethods_.size()) { - throw std::invalid_argument( - "methodId " + std::to_string(reactMethodId) + " out of range [0.." + - std::to_string(syncMethods_.size()) + "]"); + throw std::invalid_argument(folly::to( + "methodId ", + reactMethodId, + " out of range [0..", + syncMethods_.size(), + "]")); } auto& methodInvoker = syncMethods_[reactMethodId]; + if (!methodInvoker.has_value()) { - throw std::invalid_argument( - "methodId " + std::to_string(reactMethodId) + - " is not a recognized sync method"); + throw std::invalid_argument(folly::to( + "methodId ", reactMethodId, " is not a recognized sync method")); } return methodInvoker->getMethodName(); @@ -96,12 +101,15 @@ std::vector JavaNativeModule::getMethods() { } folly::dynamic JavaNativeModule::getConstants() { + TraceSection s("JavaNativeModule::getConstants", "module", getName()); static auto constantsMethod = wrapper_->getClass()->getMethod("getConstants"); auto constants = constantsMethod(wrapper_); if (!constants) { return nullptr; } else { + TraceSection s2( + "JavaNativeModule::getConstants::consume", "module", getName()); return cthis(constants)->consume(); } } @@ -118,7 +126,7 @@ void JavaNativeModule::invoke( "invoke"); #ifdef WITH_FBSYSTRACE if (callId != -1) { - fbsystrace_end_async_flow(TRACE_TAG_REACT, "native", callId); + fbsystrace_end_async_flow(TRACE_TAG_REACT_APPS, "native", callId); } #endif invokeMethod( @@ -133,9 +141,12 @@ MethodCallResult JavaNativeModule::callSerializableNativeHook( folly::dynamic&& params) { // TODO: evaluate whether calling through invoke is potentially faster if (reactMethodId >= syncMethods_.size()) { - throw std::invalid_argument( - "methodId " + std::to_string(reactMethodId) + " out of range [0.." + - std::to_string(syncMethods_.size()) + "]"); + throw std::invalid_argument(folly::to( + "methodId ", + reactMethodId, + " out of range [0..", + syncMethods_.size(), + "]")); } auto& method = syncMethods_[reactMethodId];