diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java index ead07f95122..91050e8fde9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java @@ -8,6 +8,7 @@ package com.facebook.react.animated; import androidx.annotation.Nullable; +import androidx.annotation.UiThread; import com.facebook.common.logging.FLog; import com.facebook.fbreact.specs.NativeAnimatedModuleSpec; import com.facebook.infer.annotation.Assertions; @@ -16,6 +17,8 @@ import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.UIManager; +import com.facebook.react.bridge.UIManagerListener; import com.facebook.react.bridge.WritableMap; import com.facebook.react.common.ReactConstants; import com.facebook.react.common.annotations.VisibleForTesting; @@ -26,7 +29,6 @@ import com.facebook.react.uimanager.GuardedFrameCallback; import com.facebook.react.uimanager.NativeViewHierarchyManager; import com.facebook.react.uimanager.UIBlock; import com.facebook.react.uimanager.UIManagerModule; -import com.facebook.react.uimanager.UIManagerModuleListener; import java.util.ArrayList; /** @@ -75,7 +77,7 @@ import java.util.ArrayList; */ @ReactModule(name = NativeAnimatedModule.NAME) public class NativeAnimatedModule extends NativeAnimatedModuleSpec - implements LifecycleEventListener, UIManagerModuleListener { + implements LifecycleEventListener, UIManagerListener { public static final String NAME = "NativeAnimatedModule"; @@ -132,7 +134,7 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec reactApplicationContext.addLifecycleEventListener(this); UIManagerModule uiManager = Assertions.assertNotNull(reactApplicationContext.getNativeModule(UIManagerModule.class)); - uiManager.addUIManagerListener(this); + uiManager.addUIManagerEventListener(this); } } @@ -141,8 +143,19 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec enqueueFrameCallback(); } + // For FabricUIManager @Override - public void willDispatchViewUpdates(final UIManagerModule uiManager) { + public void willDispatchPreMountItems() {} + + // For FabricUIManager + @Override + @UiThread + public void willDispatchMountItems() {} + + // For non-FabricUIManager + @Override + @UiThread + public void willDispatchViewUpdates(final UIManager uiManager) { if (mOperations.isEmpty() && mPreOperations.isEmpty()) { return; } @@ -150,7 +163,15 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec final ArrayList operations = mOperations; mPreOperations = new ArrayList<>(); mOperations = new ArrayList<>(); - uiManager.prependUIBlock( + + // This is kind of a hack. Basically UIManagerListener cannot import UIManagerModule + // (that would cause an import cycle) and they're not in the same package. But, + // UIManagerModule is the only thing that calls `willDispatchViewUpdates` so we + // know this is safe. + // This goes away entirely in Fabric/Venice. + UIManagerModule uiManagerModule = (UIManagerModule) uiManager; + + uiManagerModule.prependUIBlock( new UIBlock() { @Override public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) { @@ -160,7 +181,7 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec } } }); - uiManager.addUIBlock( + uiManagerModule.addUIBlock( new UIBlock() { @Override public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) { 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 170fc7fcd3f..4edebdcf00e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.java @@ -91,4 +91,19 @@ public interface UIManager extends JSIModule, PerformanceCounter { * @param eventType */ void sendAccessibilityEvent(int reactTag, int eventType); + + /** + * Register a {@link UIManagerListener} with this UIManager to receive lifecycle callbacks. + * + * @param listener + */ + void addUIManagerEventListener(UIManagerListener listener); + + /** + * Unregister a {@link UIManagerListener} from this UIManager to stop receiving lifecycle + * callbacks. + * + * @param listener + */ + void removeUIManagerEventListener(UIManagerListener listener); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManagerListener.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManagerListener.java new file mode 100644 index 00000000000..216d38b00d6 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManagerListener.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) Facebook, Inc. and its 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; + +/** Listener used to hook into the UIManager update process. */ +public interface UIManagerListener { + /** + * Called right before view updates are dispatched at the end of a batch. This is useful if a + * module needs to add UIBlocks to the queue before it is flushed. + */ + void willDispatchViewUpdates(UIManager uiManager); + /** Called on the UI thread right before normal mount items are executed. */ + void willDispatchMountItems(); + /** Called on the UI thread right before premount items are executed. */ + void willDispatchPreMountItems(); +} 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 8fa0963db2a..b80ee9f25c5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java @@ -42,6 +42,7 @@ import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.RetryableMountingLayerException; import com.facebook.react.bridge.UIManager; +import com.facebook.react.bridge.UIManagerListener; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.bridge.WritableMap; import com.facebook.react.config.ReactFeatureFlags; @@ -82,6 +83,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; @SuppressLint("MissingNativeLoadLibrary") public class FabricUIManager implements UIManager, LifecycleEventListener { @@ -123,6 +125,9 @@ public class FabricUIManager implements UIManager, LifecycleEventListener { @NonNull private List mViewCommandMountItems = new ArrayList<>(); + @NonNull + private final CopyOnWriteArrayList mListeners = new CopyOnWriteArrayList<>(); + @GuardedBy("mMountItemsLock") @NonNull private List mMountItems = new ArrayList<>(); @@ -500,6 +505,14 @@ public class FabricUIManager implements UIManager, LifecycleEventListener { } } + public void addUIManagerEventListener(UIManagerListener listener) { + mListeners.add(listener); + } + + public void removeUIManagerEventListener(UIManagerListener listener) { + mListeners.remove(listener); + } + /** * This method enqueues UI operations directly to the UI thread. This might change in the future * to enforce execution order using {@link ReactChoreographer#CallbackType}. @@ -581,6 +594,10 @@ public class FabricUIManager implements UIManager, LifecycleEventListener { return; } + for (UIManagerListener listener : mListeners) { + listener.willDispatchMountItems(); + } + final boolean didDispatchItems; try { didDispatchItems = dispatchMountItems(); @@ -794,6 +811,10 @@ public class FabricUIManager implements UIManager, LifecycleEventListener { // reentering during dispatchPreMountItems mInDispatch = true; + for (UIManagerListener listener : mListeners) { + listener.willDispatchPreMountItems(); + } + try { while (true) { long timeLeftInFrame = FRAME_TIME_MS - ((System.nanoTime() - frameTimeNanos) / 1000000); 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 87d46f7a962..bf8f9e77f88 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -34,6 +34,7 @@ import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableType; import com.facebook.react.bridge.UIManager; +import com.facebook.react.bridge.UIManagerListener; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.bridge.WritableMap; import com.facebook.react.common.MapBuilder; @@ -117,6 +118,7 @@ public class UIManagerModule extends ReactContextBaseJavaModule private final UIImplementation mUIImplementation; private final MemoryTrimCallback mMemoryTrimCallback = new MemoryTrimCallback(); private final List mListeners = new ArrayList<>(); + private final List mUIManagerListeners = new ArrayList<>(); private @Nullable Map mViewManagerConstantsCache; private volatile int mViewManagerConstantsCacheSize; @@ -793,6 +795,9 @@ public class UIManagerModule extends ReactContextBaseJavaModule for (UIManagerModuleListener listener : mListeners) { listener.willDispatchViewUpdates(this); } + for (UIManagerListener listener : mUIManagerListeners) { + listener.willDispatchViewUpdates(this); + } try { mUIImplementation.dispatchViewUpdates(batchId); } finally { @@ -850,14 +855,24 @@ public class UIManagerModule extends ReactContextBaseJavaModule mUIImplementation.prependUIBlock(block); } + @Deprecated public void addUIManagerListener(UIManagerModuleListener listener) { mListeners.add(listener); } + @Deprecated public void removeUIManagerListener(UIManagerModuleListener listener) { mListeners.remove(listener); } + public void addUIManagerEventListener(UIManagerListener listener) { + mUIManagerListeners.add(listener); + } + + public void removeUIManagerEventListener(UIManagerListener listener) { + mUIManagerListeners.remove(listener); + } + /** * Given a reactTag from a component, find its root node tag, if possible. Otherwise, this will * return 0. If the reactTag belongs to a root node, this will return the same reactTag. diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleListener.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleListener.java index 270d0dcf650..19163fd6c2d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleListener.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleListener.java @@ -7,7 +7,11 @@ package com.facebook.react.uimanager; -/** Listener used to hook into the UIManager update process. */ +/** + * Listener used to hook into the UIManager update process. Deprecated: use UIManagerListener + * instead. This will be deleted in some future release. + */ +@Deprecated public interface UIManagerModuleListener { /** * Called right before view updates are dispatched at the end of a batch. This is useful if a