diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/FabricEventDispatcher.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/FabricEventDispatcher.java deleted file mode 100644 index 77a3c8e6c074a7..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/FabricEventDispatcher.java +++ /dev/null @@ -1,257 +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.events; - -import android.os.Handler; -import android.view.Choreographer; -import com.facebook.infer.annotation.Nullsafe; -import com.facebook.react.bridge.LifecycleEventListener; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactNoCrashSoftException; -import com.facebook.react.bridge.ReactSoftExceptionLogger; -import com.facebook.react.bridge.UIManager; -import com.facebook.react.bridge.UiThreadUtil; -import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags; -import com.facebook.react.modules.core.ReactChoreographer; -import com.facebook.react.uimanager.UIManagerHelper; -import com.facebook.react.uimanager.common.UIManagerType; -import com.facebook.systrace.Systrace; -import java.util.concurrent.CopyOnWriteArrayList; - -/** - * A singleton class that overrides {@link EventDispatcher} with no-op methods, to be used by - * callers that expect an EventDispatcher when the instance doesn't exist. - */ -@Nullsafe(Nullsafe.Mode.LOCAL) -public class FabricEventDispatcher implements EventDispatcher, LifecycleEventListener { - private static final Handler uiThreadHandler = UiThreadUtil.getUiThreadHandler(); - - private final ReactEventEmitter mReactEventEmitter; - private final ReactApplicationContext mReactContext; - private final CopyOnWriteArrayList mListeners = - new CopyOnWriteArrayList<>(); - private final CopyOnWriteArrayList mPostEventDispatchListeners = - new CopyOnWriteArrayList<>(); - private final FabricEventDispatcher.ScheduleDispatchFrameCallback mCurrentFrameCallback = - new FabricEventDispatcher.ScheduleDispatchFrameCallback(); - - private boolean mIsDispatchScheduled = false; - private final Runnable mDispatchEventsRunnable = - new Runnable() { - @Override - public void run() { - mIsDispatchScheduled = false; - - Systrace.beginSection( - Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "BatchEventDispatchedListeners"); - try { - for (BatchEventDispatchedListener listener : mPostEventDispatchListeners) { - listener.onBatchEventDispatched(); - } - } finally { - Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); - } - } - }; - - public FabricEventDispatcher(ReactApplicationContext reactContext) { - mReactContext = reactContext; - mReactContext.addLifecycleEventListener(this); - mReactEventEmitter = new ReactEventEmitter(mReactContext); - } - - @Override - public void dispatchEvent(Event event) { - for (EventDispatcherListener listener : mListeners) { - listener.onEventDispatch(event); - } - if (event.experimental_isSynchronous()) { - dispatchSynchronous(event); - } else { - event.dispatchModern(mReactEventEmitter); - } - - event.dispose(); - scheduleDispatchOfBatchedEvents(); - } - - private void dispatchSynchronous(Event event) { - Systrace.beginSection( - Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, - "FabricEventDispatcher.dispatchSynchronous('" + event.getEventName() + "')"); - try { - UIManager fabricUIManager = UIManagerHelper.getUIManager(mReactContext, UIManagerType.FABRIC); - if (fabricUIManager instanceof SynchronousEventReceiver) { - ((SynchronousEventReceiver) fabricUIManager) - .receiveEvent( - event.getSurfaceId(), - event.getViewTag(), - event.getEventName(), - event.canCoalesce(), - event.getEventData(), - event.getEventCategory(), - true); - } else { - ReactSoftExceptionLogger.logSoftException( - "FabricEventDispatcher", - new ReactNoCrashSoftException( - "Fabric UIManager expected to implement SynchronousEventReceiver.")); - } - } finally { - Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); - } - } - - public void dispatchAllEvents() { - scheduleDispatchOfBatchedEvents(); - } - - private void scheduleDispatchOfBatchedEvents() { - if (ReactNativeFeatureFlags.useOptimizedEventBatchingOnAndroid()) { - if (!mIsDispatchScheduled) { - mIsDispatchScheduled = true; - uiThreadHandler.postAtFrontOfQueue(mDispatchEventsRunnable); - } - } else { - mCurrentFrameCallback.maybeScheduleDispatchOfBatchedEvents(); - } - } - - /** Add a listener to this EventDispatcher. */ - public void addListener(EventDispatcherListener listener) { - mListeners.add(listener); - } - - /** Remove a listener from this EventDispatcher. */ - public void removeListener(EventDispatcherListener listener) { - mListeners.remove(listener); - } - - public void addBatchEventDispatchedListener(BatchEventDispatchedListener listener) { - mPostEventDispatchListeners.add(listener); - } - - public void removeBatchEventDispatchedListener(BatchEventDispatchedListener listener) { - mPostEventDispatchListeners.remove(listener); - } - - @Override - public void onHostResume() { - scheduleDispatchOfBatchedEvents(); - if (!ReactNativeFeatureFlags.useOptimizedEventBatchingOnAndroid()) { - mCurrentFrameCallback.resume(); - } - } - - @Override - public void onHostPause() { - cancelDispatchOfBatchedEvents(); - } - - @Override - public void onHostDestroy() { - cancelDispatchOfBatchedEvents(); - } - - public void onCatalystInstanceDestroyed() { - UiThreadUtil.runOnUiThread( - new Runnable() { - @Override - public void run() { - cancelDispatchOfBatchedEvents(); - } - }); - } - - private void cancelDispatchOfBatchedEvents() { - UiThreadUtil.assertOnUiThread(); - if (ReactNativeFeatureFlags.useOptimizedEventBatchingOnAndroid()) { - mIsDispatchScheduled = false; - uiThreadHandler.removeCallbacks(mDispatchEventsRunnable); - } else { - mCurrentFrameCallback.stop(); - } - } - - public void registerEventEmitter(@UIManagerType int uiManagerType, RCTEventEmitter eventEmitter) { - mReactEventEmitter.register(uiManagerType, eventEmitter); - } - - public void registerEventEmitter( - @UIManagerType int uiManagerType, RCTModernEventEmitter eventEmitter) { - mReactEventEmitter.register(uiManagerType, eventEmitter); - } - - public void unregisterEventEmitter(@UIManagerType int uiManagerType) { - mReactEventEmitter.unregister(uiManagerType); - } - - private class ScheduleDispatchFrameCallback implements Choreographer.FrameCallback { - private volatile boolean mIsDispatchScheduled = false; - private boolean mShouldStop = false; - - @Override - public void doFrame(long frameTimeNanos) { - UiThreadUtil.assertOnUiThread(); - - if (mShouldStop) { - mIsDispatchScheduled = false; - } else { - dispatchBatchedEvents(); - } - - Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "BatchEventDispatchedListeners"); - try { - for (BatchEventDispatchedListener listener : mPostEventDispatchListeners) { - listener.onBatchEventDispatched(); - } - } finally { - Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); - } - } - - public void stop() { - mShouldStop = true; - } - - public void resume() { - mShouldStop = false; - } - - public void maybeDispatchBatchedEvents() { - if (!mIsDispatchScheduled) { - mIsDispatchScheduled = true; - dispatchBatchedEvents(); - } - } - - private void dispatchBatchedEvents() { - ReactChoreographer.getInstance() - .postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, mCurrentFrameCallback); - } - - public void maybeScheduleDispatchOfBatchedEvents() { - if (mIsDispatchScheduled) { - return; - } - - // We should only hit this slow path when we receive events while the host activity is paused. - if (mReactContext.isOnUiQueueThread()) { - maybeDispatchBatchedEvents(); - } else { - mReactContext.runOnUiQueueThread( - new Runnable() { - @Override - public void run() { - maybeDispatchBatchedEvents(); - } - }); - } - } - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/FabricEventDispatcher.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/FabricEventDispatcher.kt new file mode 100644 index 00000000000000..d5e34a493d1fa5 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/FabricEventDispatcher.kt @@ -0,0 +1,241 @@ +/* + * 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.events + +import android.os.Handler +import android.view.Choreographer +import com.facebook.react.bridge.LifecycleEventListener +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactNoCrashSoftException +import com.facebook.react.bridge.ReactSoftExceptionLogger +import com.facebook.react.bridge.UIManager +import com.facebook.react.bridge.UiThreadUtil +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags +import com.facebook.react.modules.core.ReactChoreographer +import com.facebook.react.uimanager.UIManagerHelper +import com.facebook.react.uimanager.common.UIManagerType +import com.facebook.systrace.Systrace +import java.util.concurrent.CopyOnWriteArrayList +import kotlin.concurrent.Volatile + +/** + * A singleton class that overrides [EventDispatcher] with no-op methods, to be used by callers that + * expect an EventDispatcher when the instance doesn't exist. + */ +public open class FabricEventDispatcher(reactContext: ReactApplicationContext) : + EventDispatcher, LifecycleEventListener { + private val reactEventEmitter: ReactEventEmitter + private val reactContext: ReactApplicationContext = reactContext + private val listeners = CopyOnWriteArrayList() + private val postEventDispatchListeners = CopyOnWriteArrayList() + private val currentFrameCallback: ScheduleDispatchFrameCallback = ScheduleDispatchFrameCallback() + + private var isDispatchScheduled = false + private val dispatchEventsRunnable = Runnable { + isDispatchScheduled = false + Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "BatchEventDispatchedListeners") + try { + for (listener in postEventDispatchListeners) { + listener.onBatchEventDispatched() + } + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE) + } + } + + init { + this.reactContext.addLifecycleEventListener(this) + reactEventEmitter = ReactEventEmitter(this.reactContext) + } + + public override fun dispatchEvent(event: Event<*>) { + for (listener in listeners) { + listener.onEventDispatch(event) + } + if (event.experimental_isSynchronous()) { + dispatchSynchronous(event) + } else { + event.dispatchModern(reactEventEmitter) + } + + event.dispose() + scheduleDispatchOfBatchedEvents() + } + + private fun dispatchSynchronous(event: Event<*>) { + Systrace.beginSection( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, + "FabricEventDispatcher.dispatchSynchronous('" + event.eventName + "')") + try { + val fabricUIManager: UIManager? = + UIManagerHelper.getUIManager(reactContext, UIManagerType.FABRIC) + @Suppress("DEPRECATION") + if (fabricUIManager is SynchronousEventReceiver) { + @Suppress("DEPRECATION") + (fabricUIManager as SynchronousEventReceiver).receiveEvent( + event.surfaceId, + event.viewTag, + event.eventName, + event.canCoalesce(), + event.eventData, + event.eventCategory, + true) + } else { + ReactSoftExceptionLogger.logSoftException( + "FabricEventDispatcher", + ReactNoCrashSoftException( + "Fabric UIManager expected to implement SynchronousEventReceiver.")) + } + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE) + } + } + + public override fun dispatchAllEvents() { + scheduleDispatchOfBatchedEvents() + } + + private fun scheduleDispatchOfBatchedEvents() { + if (ReactNativeFeatureFlags.useOptimizedEventBatchingOnAndroid()) { + if (!isDispatchScheduled) { + isDispatchScheduled = true + uiThreadHandler.postAtFrontOfQueue(dispatchEventsRunnable) + } + } else { + currentFrameCallback.maybeScheduleDispatchOfBatchedEvents() + } + } + + /** Add a listener to this EventDispatcher. */ + public override fun addListener(listener: EventDispatcherListener) { + listeners.add(listener) + } + + /** Remove a listener from this EventDispatcher. */ + public override fun removeListener(listener: EventDispatcherListener) { + listeners.remove(listener) + } + + public override fun addBatchEventDispatchedListener(listener: BatchEventDispatchedListener) { + postEventDispatchListeners.add(listener) + } + + public override fun removeBatchEventDispatchedListener(listener: BatchEventDispatchedListener) { + postEventDispatchListeners.remove(listener) + } + + public override fun onHostResume() { + scheduleDispatchOfBatchedEvents() + if (!ReactNativeFeatureFlags.useOptimizedEventBatchingOnAndroid()) { + currentFrameCallback.resume() + } + } + + public override fun onHostPause() { + cancelDispatchOfBatchedEvents() + } + + public override fun onHostDestroy() { + cancelDispatchOfBatchedEvents() + } + + public override fun onCatalystInstanceDestroyed() { + UiThreadUtil.runOnUiThread(Runnable { cancelDispatchOfBatchedEvents() }) + } + + private fun cancelDispatchOfBatchedEvents() { + UiThreadUtil.assertOnUiThread() + if (ReactNativeFeatureFlags.useOptimizedEventBatchingOnAndroid()) { + isDispatchScheduled = false + uiThreadHandler.removeCallbacks(dispatchEventsRunnable) + } else { + currentFrameCallback.stop() + } + } + + @Deprecated("Use the modern version with RCTModernEventEmitter") + @Suppress("DEPRECATION") + public override fun registerEventEmitter( + @UIManagerType uiManagerType: Int, + eventEmitter: RCTEventEmitter + ) { + reactEventEmitter.register(uiManagerType, eventEmitter) + } + + public override fun registerEventEmitter( + @UIManagerType uiManagerType: Int, + eventEmitter: RCTModernEventEmitter + ) { + reactEventEmitter.register(uiManagerType, eventEmitter) + } + + public override fun unregisterEventEmitter(@UIManagerType uiManagerType: Int) { + reactEventEmitter.unregister(uiManagerType) + } + + private inner class ScheduleDispatchFrameCallback : Choreographer.FrameCallback { + @Volatile private var isFrameCallbackDispatchScheduled = false + private var shouldStop = false + + override fun doFrame(frameTimeNanos: Long) { + UiThreadUtil.assertOnUiThread() + + if (shouldStop) { + isFrameCallbackDispatchScheduled = false + } else { + dispatchBatchedEvents() + } + + Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "BatchEventDispatchedListeners") + try { + for (listener in postEventDispatchListeners) { + listener.onBatchEventDispatched() + } + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE) + } + } + + public fun stop() { + shouldStop = true + } + + public fun resume() { + shouldStop = false + } + + public fun maybeDispatchBatchedEvents() { + if (!isFrameCallbackDispatchScheduled) { + isFrameCallbackDispatchScheduled = true + dispatchBatchedEvents() + } + } + + private fun dispatchBatchedEvents() { + ReactChoreographer.getInstance() + .postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, currentFrameCallback) + } + + public fun maybeScheduleDispatchOfBatchedEvents() { + if (isFrameCallbackDispatchScheduled) { + return + } + + // We should only hit this slow path when we receive events while the host activity is paused. + if (reactContext.isOnUiQueueThread()) { + maybeDispatchBatchedEvents() + } else { + reactContext.runOnUiQueueThread(Runnable { maybeDispatchBatchedEvents() }) + } + } + } + + private companion object { + private val uiThreadHandler: Handler = UiThreadUtil.getUiThreadHandler() + } +}