diff --git a/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java b/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java index e248062473..7d1729782d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java +++ b/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java @@ -88,4 +88,7 @@ public class ReactFeatureFlags { /** Temporary flag for FB-internal workaround for RN:Litho interop in non-Fabric RN. */ public static boolean enableNonFabricRNLithoForceLayout = true; + + /** Disable UI update operations in non-Fabric renderer after catalyst instance was destroyed */ + public static boolean disableNonFabricViewOperationsOnCatalystDestroy = false; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java index 16630deff0..08ae573089 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java @@ -51,6 +51,13 @@ public class UIImplementation { private long mLastCalculateLayoutTime = 0; protected @Nullable LayoutUpdateListener mLayoutUpdateListener; + /** + * When react instance is being shutdown, there could be some pending operations queued in the JS + * thread. This flag ensures view related operations are not triggered if the Catalyst instance + * was destroyed. + */ + private volatile boolean mViewOperationsEnabled = true; + /** Interface definition for a callback to be invoked when the layout has been updated */ public interface LayoutUpdateListener { @@ -234,6 +241,10 @@ public class UIImplementation { /** Invoked by React to create a new node with a given tag, class name and properties. */ public void createView(int tag, String className, int rootViewTag, ReadableMap props) { + if (!mViewOperationsEnabled) { + return; + } + synchronized (uiImplementationThreadLock) { ReactShadowNode cssNode = createShadowNode(className); ReactShadowNode rootNode = mShadowNodeRegistry.getNode(rootViewTag); @@ -264,6 +275,10 @@ public class UIImplementation { /** Invoked by React to create a new node with a given tag has its properties changed. */ public void updateView(int tag, String className, ReadableMap props) { + if (!mViewOperationsEnabled) { + return; + } + ViewManager viewManager = mViewManagers.get(className); if (viewManager == null) { throw new IllegalViewOperationException("Got unknown view type: " + className); @@ -314,6 +329,10 @@ public class UIImplementation { @Nullable ReadableArray addChildTags, @Nullable ReadableArray addAtIndices, @Nullable ReadableArray removeFrom) { + if (!mViewOperationsEnabled) { + return; + } + synchronized (uiImplementationThreadLock) { ReactShadowNode cssNodeToManage = mShadowNodeRegistry.getNode(viewTag); @@ -420,6 +439,10 @@ public class UIImplementation { * @param childrenTags tags of the children */ public void setChildren(int viewTag, ReadableArray childrenTags) { + if (!mViewOperationsEnabled) { + return; + } + synchronized (uiImplementationThreadLock) { ReactShadowNode cssNodeToManage = mShadowNodeRegistry.getNode(viewTag); @@ -530,6 +553,10 @@ public class UIImplementation { * view and returns the values via an async callback. */ public void measure(int reactTag, Callback callback) { + if (!mViewOperationsEnabled) { + return; + } + // This method is called by the implementation of JS touchable interface (see Touchable.js for // more details) at the moment of touch activation. That is after user starts the gesture from // a touchable view with a given reactTag, or when user drag finger back into the press @@ -543,6 +570,10 @@ public class UIImplementation { * things like the status bar */ public void measureInWindow(int reactTag, Callback callback) { + if (!mViewOperationsEnabled) { + return; + } + mOperationsQueue.enqueueMeasureInWindow(reactTag, callback); } @@ -554,6 +585,10 @@ public class UIImplementation { */ public void measureLayout( int tag, int ancestorTag, Callback errorCallback, Callback successCallback) { + if (!mViewOperationsEnabled) { + return; + } + try { measureLayout(tag, ancestorTag, mMeasureBuffer); float relativeX = PixelUtil.toDIPFromPixel(mMeasureBuffer[0]); @@ -571,6 +606,10 @@ public class UIImplementation { */ public void measureLayoutRelativeToParent( int tag, Callback errorCallback, Callback successCallback) { + if (!mViewOperationsEnabled) { + return; + } + try { measureLayoutRelativeToParent(tag, mMeasureBuffer); float relativeX = PixelUtil.toDIPFromPixel(mMeasureBuffer[0]); @@ -747,6 +786,12 @@ public class UIImplementation { public void onHostDestroy() {} + public void onCatalystInstanceDestroyed() { + if (ReactFeatureFlags.disableNonFabricViewOperationsOnCatalystDestroy) { + mViewOperationsEnabled = false; + } + } + public void setViewHierarchyUpdateDebugListener( @Nullable NotThreadSafeViewHierarchyUpdateDebugListener listener) { mOperationsQueue.setViewHierarchyUpdateDebugListener(listener); 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 c5340ae3b7..2f2e825757 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -242,6 +242,7 @@ public class UIManagerModule extends ReactContextBaseJavaModule public void onCatalystInstanceDestroy() { super.onCatalystInstanceDestroy(); mEventDispatcher.onCatalystInstanceDestroyed(); + mUIImplementation.onCatalystInstanceDestroyed(); getReactApplicationContext().unregisterComponentCallbacks(mMemoryTrimCallback); YogaNodePool.get().clear();