diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java b/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java index da3b1dbac03..ddea201e600 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java @@ -127,8 +127,11 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setAllowImmediateUIOperationExecution(false); + if (mUseSurface) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); + setAllowImmediateUIOperationExecution(true); return; } @@ -182,6 +185,7 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot { mLastHeight = height; } finally { + setAllowImmediateUIOperationExecution(true); Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE); } } @@ -437,6 +441,38 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot { } } + /** + * In Fabric, it is possible for MountItems to be scheduled during onMeasure calls, specifically: + * + *

ReactRootView.onMeasure -> ReactRootView.updateRootLayoutSpecs -> + * FabricUIManager.updateRootLayoutSpecs -> Binding.setConstraints -> (C++) commit new tree -> + * (C++ Android binding) diff tree, schedule mount items -> FabricUIManager.scheduleMountItem + * + *

If called on the main thread, `scheduleMountItem` will execute MountItems synchronously, + * causing all ShadowNode updates to be flushed to the view hierarchy, on the main thread, during + * an onMeasure call. + * + *

Use this method to disable immediate execution of mount items. + * + *

This is a noop outside in pre-Fabric React Native. + */ + private void setAllowImmediateUIOperationExecution(boolean flag) { + final ReactInstanceManager reactInstanceManager = mReactInstanceManager; + + if (reactInstanceManager == null) { + return; + } + + final ReactContext reactApplicationContext = reactInstanceManager.getCurrentReactContext(); + + if (reactApplicationContext == null) { + return; + } + + UIManagerHelper.getUIManager(reactApplicationContext, getUIManagerType()) + .setAllowImmediateUIOperationExecution(flag); + } + /** * Unmount the react application at this root view, reclaiming any JS memory associated with that * application. If {@link #startReactApplication} is called, this method must be called before the diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.java index 59c41a090c4..6f3c442634e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.java @@ -62,4 +62,12 @@ public interface UIManager extends JSIModule, PerformanceCounter { * @param eventType */ void sendAccessibilityEvent(int reactTag, int eventType); + + /** + * When mounting instructions are scheduled on the UI thread, should they be executed immediately? + * For Fabric. Should noop in pre-Fabric. + * + * @param flag + */ + void setAllowImmediateUIOperationExecution(boolean flag); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java index 3cb20d1429d..9f4f9426e11 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java @@ -125,6 +125,13 @@ public class FabricUIManager implements UIManager, LifecycleEventListener { @NonNull private final DispatchUIFrameCallback mDispatchUIFrameCallback; + /** + * Whether or not to immediately, synchronously execute mountItems when they are scheduled on the + * UI thread. + */ + @ThreadConfined(UI) + private boolean mImmediatelyExecutedMountItemsOnUI = true; + /** * This is used to keep track of whether or not the FabricUIManager has been destroyed. Once the * Catalyst instance is being destroyed, we should cease all operation here. @@ -446,6 +453,16 @@ public class FabricUIManager implements UIManager, LifecycleEventListener { } } + /** + * When mounting instructions are scheduled on the UI thread, should they be executed immediately? + * * + */ + @Override + @ThreadConfined(UI) + public void setAllowImmediateUIOperationExecution(boolean flag) { + mImmediatelyExecutedMountItemsOnUI = flag; + } + /** * This method enqueues UI operations directly to the UI thread. This might change in the future * to enforce execution order using {@link ReactChoreographer#CallbackType}. @@ -480,7 +497,7 @@ public class FabricUIManager implements UIManager, LifecycleEventListener { mMountItems.add(mountItem); } - if (UiThreadUtil.isOnUiThread()) { + if (mImmediatelyExecutedMountItemsOnUI && UiThreadUtil.isOnUiThread()) { dispatchMountItems(); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index f0a8910de05..7689411afd5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -828,6 +828,11 @@ public class UIManagerModule extends ReactContextBaseJavaModule } } + @Override + public void setAllowImmediateUIOperationExecution(boolean flag) { + // Noop outside of Fabric, call directly on FabricUIManager + } + /** * Schedule a block to be executed on the UI thread. Useful if you need to execute view logic * after all currently queued view updates have completed.