Convert FabricEventDispatcher (#48491)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/48491

# Changelog:
[Internal] -

As in the title.

Reviewed By: christophpurrer

Differential Revision: D67825655

fbshipit-source-id: e94431ae6978332c0566ab0a500e1a85f1ad9a7d
This commit is contained in:
Ruslan Shestopalyuk
2025-01-06 01:46:46 -08:00
committed by Facebook GitHub Bot
parent a9d86be3a4
commit 5ce07386e1
2 changed files with 241 additions and 257 deletions
@@ -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<EventDispatcherListener> mListeners =
new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<BatchEventDispatchedListener> 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();
}
});
}
}
}
}
@@ -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<EventDispatcherListener>()
private val postEventDispatchListeners = CopyOnWriteArrayList<BatchEventDispatchedListener>()
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()
}
}