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 bf8f9e77f8..1f51e272ac 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java
@@ -43,6 +43,7 @@ import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.common.ViewUtil;
import com.facebook.react.uimanager.debug.NotThreadSafeViewHierarchyUpdateDebugListener;
import com.facebook.react.uimanager.events.EventDispatcher;
+import com.facebook.react.uimanager.events.EventDispatcherImpl;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.facebook.systrace.Systrace;
import com.facebook.systrace.SystraceMessage;
@@ -156,7 +157,7 @@ public class UIManagerModule extends ReactContextBaseJavaModule
int minTimeLeftInFrameForNonBatchedOperationMs) {
super(reactContext);
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(reactContext);
- mEventDispatcher = new EventDispatcher(reactContext);
+ mEventDispatcher = new EventDispatcherImpl(reactContext);
mModuleConstants = createConstants(viewManagerResolver);
mCustomDirectEvents = UIManagerModuleConstants.getDirectEventTypeConstants();
mViewManagerRegistry = new ViewManagerRegistry(viewManagerResolver);
@@ -178,7 +179,7 @@ public class UIManagerModule extends ReactContextBaseJavaModule
int minTimeLeftInFrameForNonBatchedOperationMs) {
super(reactContext);
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(reactContext);
- mEventDispatcher = new EventDispatcher(reactContext);
+ mEventDispatcher = new EventDispatcherImpl(reactContext);
mCustomDirectEvents = MapBuilder.newHashMap();
mModuleConstants = createConstants(viewManagersList, null, mCustomDirectEvents);
mViewManagerRegistry = new ViewManagerRegistry(viewManagersList);
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.java
index 68ed01c985..2ba1834097 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.java
@@ -7,383 +7,28 @@
package com.facebook.react.uimanager.events;
-import android.util.LongSparseArray;
-import com.facebook.infer.annotation.Assertions;
-import com.facebook.react.bridge.LifecycleEventListener;
-import com.facebook.react.bridge.ReactApplicationContext;
-import com.facebook.react.bridge.UiThreadUtil;
-import com.facebook.react.common.MapBuilder;
-import com.facebook.react.modules.core.ChoreographerCompat;
-import com.facebook.react.modules.core.ReactChoreographer;
import com.facebook.react.uimanager.common.UIManagerType;
-import com.facebook.systrace.Systrace;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicInteger;
-/**
- * Class responsible for dispatching UI events to JS. The main purpose of this class is to act as an
- * intermediary between UI code generating events and JS, making sure we don't send more events than
- * JS can process.
- *
- *
To use it, create a subclass of {@link Event} and call {@link #dispatchEvent(Event)} whenever
- * there's a UI event to dispatch.
- *
- *
This class works by installing a Choreographer frame callback on the main thread. This
- * callback then enqueues a runnable on the JS thread (if one is not already pending) that is
- * responsible for actually dispatch events to JS. This implementation depends on the properties
- * that 1) FrameCallbacks run after UI events have been processed in Choreographer.java 2) when we
- * enqueue a runnable on the JS queue thread, it won't be called until after any previously enqueued
- * JS jobs have finished processing
- *
- *
If JS is taking a long time processing events, then the UI events generated on the UI thread
- * can be coalesced into fewer events so that when the runnable runs, we don't overload JS with a
- * ton of events and make it get even farther behind.
- *
- *
Ideally, we don't need this and JS is fast enough to process all the events each frame, but
- * bad things happen, including load on CPUs from the system, and we should handle this case well.
- *
- *
== Event Cookies ==
- *
- *
An event cookie is made up of the event type id, view tag, and a custom coalescing key. Only
- * Events that have the same cookie can be coalesced.
- *
- *
Event Cookie Composition: VIEW_TAG_MASK = 0x00000000ffffffff EVENT_TYPE_ID_MASK =
- * 0x0000ffff00000000 COALESCING_KEY_MASK = 0xffff000000000000
- */
-public class EventDispatcher implements LifecycleEventListener {
-
- private static final Comparator EVENT_COMPARATOR =
- new Comparator() {
- @Override
- public int compare(Event lhs, Event rhs) {
- if (lhs == null && rhs == null) {
- return 0;
- }
- if (lhs == null) {
- return -1;
- }
- if (rhs == null) {
- return 1;
- }
-
- long diff = lhs.getTimestampMs() - rhs.getTimestampMs();
- if (diff == 0) {
- return 0;
- } else if (diff < 0) {
- return -1;
- } else {
- return 1;
- }
- }
- };
-
- private final Object mEventsStagingLock = new Object();
- private final Object mEventsToDispatchLock = new Object();
- private final ReactApplicationContext mReactContext;
- private final LongSparseArray mEventCookieToLastEventIdx = new LongSparseArray<>();
- private final Map mEventNameToEventId = MapBuilder.newHashMap();
- private final DispatchEventsRunnable mDispatchEventsRunnable = new DispatchEventsRunnable();
- private final ArrayList mEventStaging = new ArrayList<>();
- private final ArrayList mListeners = new ArrayList<>();
- private final List mPostEventDispatchListeners = new ArrayList<>();
- private final ScheduleDispatchFrameCallback mCurrentFrameCallback =
- new ScheduleDispatchFrameCallback();
- private final AtomicInteger mHasDispatchScheduledCount = new AtomicInteger();
-
- private Event[] mEventsToDispatch = new Event[16];
- private int mEventsToDispatchSize = 0;
- private volatile ReactEventEmitter mReactEventEmitter;
- private short mNextEventTypeId = 0;
- private volatile boolean mHasDispatchScheduled = false;
-
- public EventDispatcher(ReactApplicationContext reactContext) {
- mReactContext = reactContext;
- mReactContext.addLifecycleEventListener(this);
- mReactEventEmitter = new ReactEventEmitter(mReactContext);
- }
+public interface EventDispatcher {
/** Sends the given Event to JS, coalescing eligible events if JS is backed up. */
- public void dispatchEvent(Event event) {
- Assertions.assertCondition(event.isInitialized(), "Dispatched event hasn't been initialized");
+ void dispatchEvent(Event event);
- for (EventDispatcherListener listener : mListeners) {
- listener.onEventDispatch(event);
- }
-
- synchronized (mEventsStagingLock) {
- mEventStaging.add(event);
- Systrace.startAsyncFlow(
- Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, event.getEventName(), event.getUniqueID());
- }
- maybePostFrameCallbackFromNonUI();
- }
-
- public void dispatchAllEvents() {
- maybePostFrameCallbackFromNonUI();
- }
-
- private void maybePostFrameCallbackFromNonUI() {
- if (mReactEventEmitter != null) {
- // If the host activity is paused, the frame callback may not be currently
- // posted. Ensure that it is so that this event gets delivered promptly.
- mCurrentFrameCallback.maybePostFromNonUI();
- } else {
- // No JS application has started yet, or resumed. This can happen when a ReactRootView is
- // added to view hierarchy, but ReactContext creation has not completed yet. In this case, any
- // touch event dispatch will hit this codepath, and we simply queue them so that they
- // are dispatched once ReactContext creation completes and JS app is running.
- }
- }
+ void dispatchAllEvents();
/** Add a listener to this EventDispatcher. */
- public void addListener(EventDispatcherListener listener) {
- mListeners.add(listener);
- }
+ void addListener(EventDispatcherListener listener);
/** Remove a listener from this EventDispatcher. */
- public void removeListener(EventDispatcherListener listener) {
- mListeners.remove(listener);
- }
+ void removeListener(EventDispatcherListener listener);
- public void addBatchEventDispatchedListener(BatchEventDispatchedListener listener) {
- mPostEventDispatchListeners.add(listener);
- }
+ void addBatchEventDispatchedListener(BatchEventDispatchedListener listener);
- public void removeBatchEventDispatchedListener(BatchEventDispatchedListener listener) {
- mPostEventDispatchListeners.remove(listener);
- }
+ void removeBatchEventDispatchedListener(BatchEventDispatchedListener listener);
- @Override
- public void onHostResume() {
- maybePostFrameCallbackFromNonUI();
- }
+ void registerEventEmitter(@UIManagerType int uiManagerType, RCTEventEmitter eventEmitter);
- @Override
- public void onHostPause() {
- stopFrameCallback();
- }
+ void unregisterEventEmitter(@UIManagerType int uiManagerType);
- @Override
- public void onHostDestroy() {
- stopFrameCallback();
- }
-
- public void onCatalystInstanceDestroyed() {
- UiThreadUtil.runOnUiThread(
- new Runnable() {
- @Override
- public void run() {
- stopFrameCallback();
- }
- });
- }
-
- private void stopFrameCallback() {
- UiThreadUtil.assertOnUiThread();
- mCurrentFrameCallback.stop();
- }
-
- /**
- * We use a staging data structure so that all UI events generated in a single frame are
- * dispatched at once. Otherwise, a JS runnable enqueued in a previous frame could run while the
- * UI thread is in the process of adding UI events and we might incorrectly send one event this
- * frame and another from this frame during the next.
- */
- private void moveStagedEventsToDispatchQueue() {
- synchronized (mEventsStagingLock) {
- synchronized (mEventsToDispatchLock) {
- for (int i = 0; i < mEventStaging.size(); i++) {
- Event event = mEventStaging.get(i);
-
- if (!event.canCoalesce()) {
- addEventToEventsToDispatch(event);
- continue;
- }
-
- long eventCookie =
- getEventCookie(event.getViewTag(), event.getEventName(), event.getCoalescingKey());
-
- Event eventToAdd = null;
- Event eventToDispose = null;
- Integer lastEventIdx = mEventCookieToLastEventIdx.get(eventCookie);
-
- if (lastEventIdx == null) {
- eventToAdd = event;
- mEventCookieToLastEventIdx.put(eventCookie, mEventsToDispatchSize);
- } else {
- Event lastEvent = mEventsToDispatch[lastEventIdx];
- Event coalescedEvent = event.coalesce(lastEvent);
- if (coalescedEvent != lastEvent) {
- eventToAdd = coalescedEvent;
- mEventCookieToLastEventIdx.put(eventCookie, mEventsToDispatchSize);
- eventToDispose = lastEvent;
- mEventsToDispatch[lastEventIdx] = null;
- } else {
- eventToDispose = event;
- }
- }
-
- if (eventToAdd != null) {
- addEventToEventsToDispatch(eventToAdd);
- }
- if (eventToDispose != null) {
- eventToDispose.dispose();
- }
- }
- }
- mEventStaging.clear();
- }
- }
-
- private long getEventCookie(int viewTag, String eventName, short coalescingKey) {
- short eventTypeId;
- Short eventIdObj = mEventNameToEventId.get(eventName);
- if (eventIdObj != null) {
- eventTypeId = eventIdObj;
- } else {
- eventTypeId = mNextEventTypeId++;
- mEventNameToEventId.put(eventName, eventTypeId);
- }
- return getEventCookie(viewTag, eventTypeId, coalescingKey);
- }
-
- private static long getEventCookie(int viewTag, short eventTypeId, short coalescingKey) {
- return viewTag
- | (((long) eventTypeId) & 0xffff) << 32
- | (((long) coalescingKey) & 0xffff) << 48;
- }
-
- public void registerEventEmitter(@UIManagerType int uiManagerType, RCTEventEmitter eventEmitter) {
- mReactEventEmitter.register(uiManagerType, eventEmitter);
- }
-
- public void unregisterEventEmitter(@UIManagerType int uiManagerType) {
- mReactEventEmitter.unregister(uiManagerType);
- }
-
- private class ScheduleDispatchFrameCallback extends ChoreographerCompat.FrameCallback {
- private volatile boolean mIsPosted = false;
- private boolean mShouldStop = false;
-
- @Override
- public void doFrame(long frameTimeNanos) {
- UiThreadUtil.assertOnUiThread();
-
- if (mShouldStop) {
- mIsPosted = false;
- } else {
- post();
- }
-
- Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "ScheduleDispatchFrameCallback");
- try {
- moveStagedEventsToDispatchQueue();
-
- if (!mHasDispatchScheduled) {
- mHasDispatchScheduled = true;
- Systrace.startAsyncFlow(
- Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
- "ScheduleDispatchFrameCallback",
- mHasDispatchScheduledCount.get());
- mReactContext.runOnJSQueueThread(mDispatchEventsRunnable);
- }
- } finally {
- Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
- }
- }
-
- public void stop() {
- mShouldStop = true;
- }
-
- public void maybePost() {
- if (!mIsPosted) {
- mIsPosted = true;
- post();
- }
- }
-
- private void post() {
- ReactChoreographer.getInstance()
- .postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, mCurrentFrameCallback);
- }
-
- public void maybePostFromNonUI() {
- if (mIsPosted) {
- return;
- }
-
- // We should only hit this slow path when we receive events while the host activity is paused.
- if (mReactContext.isOnUiQueueThread()) {
- maybePost();
- } else {
- mReactContext.runOnUiQueueThread(
- new Runnable() {
- @Override
- public void run() {
- maybePost();
- }
- });
- }
- }
- }
-
- private class DispatchEventsRunnable implements Runnable {
-
- @Override
- public void run() {
- Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "DispatchEventsRunnable");
- try {
- Systrace.endAsyncFlow(
- Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
- "ScheduleDispatchFrameCallback",
- mHasDispatchScheduledCount.getAndIncrement());
- mHasDispatchScheduled = false;
- Assertions.assertNotNull(mReactEventEmitter);
- synchronized (mEventsToDispatchLock) {
- if (mEventsToDispatchSize > 0) {
- // We avoid allocating an array and iterator, and "sorting" if we don't need to.
- // This occurs when the size of mEventsToDispatch is zero or one.
- if (mEventsToDispatchSize > 1) {
- Arrays.sort(mEventsToDispatch, 0, mEventsToDispatchSize, EVENT_COMPARATOR);
- }
- for (int eventIdx = 0; eventIdx < mEventsToDispatchSize; eventIdx++) {
- Event event = mEventsToDispatch[eventIdx];
- // Event can be null if it has been coalesced into another event.
- if (event == null) {
- continue;
- }
- Systrace.endAsyncFlow(
- Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, event.getEventName(), event.getUniqueID());
- event.dispatch(mReactEventEmitter);
- event.dispose();
- }
- clearEventsToDispatch();
- mEventCookieToLastEventIdx.clear();
- }
- }
- for (BatchEventDispatchedListener listener : mPostEventDispatchListeners) {
- listener.onBatchEventDispatched();
- }
- } finally {
- Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
- }
- }
- }
-
- private void addEventToEventsToDispatch(Event event) {
- if (mEventsToDispatchSize == mEventsToDispatch.length) {
- mEventsToDispatch = Arrays.copyOf(mEventsToDispatch, 2 * mEventsToDispatch.length);
- }
- mEventsToDispatch[mEventsToDispatchSize++] = event;
- }
-
- private void clearEventsToDispatch() {
- Arrays.fill(mEventsToDispatch, 0, mEventsToDispatchSize, null);
- mEventsToDispatchSize = 0;
- }
+ void onCatalystInstanceDestroyed();
}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcherImpl.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcherImpl.java
new file mode 100644
index 0000000000..542f03616f
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcherImpl.java
@@ -0,0 +1,389 @@
+/*
+ * 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.uimanager.events;
+
+import android.util.LongSparseArray;
+import com.facebook.infer.annotation.Assertions;
+import com.facebook.react.bridge.LifecycleEventListener;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.UiThreadUtil;
+import com.facebook.react.common.MapBuilder;
+import com.facebook.react.modules.core.ChoreographerCompat;
+import com.facebook.react.modules.core.ReactChoreographer;
+import com.facebook.react.uimanager.common.UIManagerType;
+import com.facebook.systrace.Systrace;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Class responsible for dispatching UI events to JS. The main purpose of this class is to act as an
+ * intermediary between UI code generating events and JS, making sure we don't send more events than
+ * JS can process.
+ *
+ * To use it, create a subclass of {@link Event} and call {@link #dispatchEvent(Event)} whenever
+ * there's a UI event to dispatch.
+ *
+ *
This class works by installing a Choreographer frame callback on the main thread. This
+ * callback then enqueues a runnable on the JS thread (if one is not already pending) that is
+ * responsible for actually dispatch events to JS. This implementation depends on the properties
+ * that 1) FrameCallbacks run after UI events have been processed in Choreographer.java 2) when we
+ * enqueue a runnable on the JS queue thread, it won't be called until after any previously enqueued
+ * JS jobs have finished processing
+ *
+ *
If JS is taking a long time processing events, then the UI events generated on the UI thread
+ * can be coalesced into fewer events so that when the runnable runs, we don't overload JS with a
+ * ton of events and make it get even farther behind.
+ *
+ *
Ideally, we don't need this and JS is fast enough to process all the events each frame, but
+ * bad things happen, including load on CPUs from the system, and we should handle this case well.
+ *
+ *
== Event Cookies ==
+ *
+ *
An event cookie is made up of the event type id, view tag, and a custom coalescing key. Only
+ * Events that have the same cookie can be coalesced.
+ *
+ *
Event Cookie Composition: VIEW_TAG_MASK = 0x00000000ffffffff EVENT_TYPE_ID_MASK =
+ * 0x0000ffff00000000 COALESCING_KEY_MASK = 0xffff000000000000
+ */
+public class EventDispatcherImpl implements EventDispatcher, LifecycleEventListener {
+
+ private static final Comparator EVENT_COMPARATOR =
+ new Comparator() {
+ @Override
+ public int compare(Event lhs, Event rhs) {
+ if (lhs == null && rhs == null) {
+ return 0;
+ }
+ if (lhs == null) {
+ return -1;
+ }
+ if (rhs == null) {
+ return 1;
+ }
+
+ long diff = lhs.getTimestampMs() - rhs.getTimestampMs();
+ if (diff == 0) {
+ return 0;
+ } else if (diff < 0) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+ };
+
+ private final Object mEventsStagingLock = new Object();
+ private final Object mEventsToDispatchLock = new Object();
+ private final ReactApplicationContext mReactContext;
+ private final LongSparseArray mEventCookieToLastEventIdx = new LongSparseArray<>();
+ private final Map mEventNameToEventId = MapBuilder.newHashMap();
+ private final DispatchEventsRunnable mDispatchEventsRunnable = new DispatchEventsRunnable();
+ private final ArrayList mEventStaging = new ArrayList<>();
+ private final ArrayList mListeners = new ArrayList<>();
+ private final List mPostEventDispatchListeners = new ArrayList<>();
+ private final ScheduleDispatchFrameCallback mCurrentFrameCallback =
+ new ScheduleDispatchFrameCallback();
+ private final AtomicInteger mHasDispatchScheduledCount = new AtomicInteger();
+
+ private Event[] mEventsToDispatch = new Event[16];
+ private int mEventsToDispatchSize = 0;
+ private volatile ReactEventEmitter mReactEventEmitter;
+ private short mNextEventTypeId = 0;
+ private volatile boolean mHasDispatchScheduled = false;
+
+ public EventDispatcherImpl(ReactApplicationContext reactContext) {
+ mReactContext = reactContext;
+ mReactContext.addLifecycleEventListener(this);
+ mReactEventEmitter = new ReactEventEmitter(mReactContext);
+ }
+
+ /** Sends the given Event to JS, coalescing eligible events if JS is backed up. */
+ public void dispatchEvent(Event event) {
+ Assertions.assertCondition(event.isInitialized(), "Dispatched event hasn't been initialized");
+
+ for (EventDispatcherListener listener : mListeners) {
+ listener.onEventDispatch(event);
+ }
+
+ synchronized (mEventsStagingLock) {
+ mEventStaging.add(event);
+ Systrace.startAsyncFlow(
+ Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, event.getEventName(), event.getUniqueID());
+ }
+ maybePostFrameCallbackFromNonUI();
+ }
+
+ public void dispatchAllEvents() {
+ maybePostFrameCallbackFromNonUI();
+ }
+
+ private void maybePostFrameCallbackFromNonUI() {
+ if (mReactEventEmitter != null) {
+ // If the host activity is paused, the frame callback may not be currently
+ // posted. Ensure that it is so that this event gets delivered promptly.
+ mCurrentFrameCallback.maybePostFromNonUI();
+ } else {
+ // No JS application has started yet, or resumed. This can happen when a ReactRootView is
+ // added to view hierarchy, but ReactContext creation has not completed yet. In this case, any
+ // touch event dispatch will hit this codepath, and we simply queue them so that they
+ // are dispatched once ReactContext creation completes and JS app is running.
+ }
+ }
+
+ /** 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() {
+ maybePostFrameCallbackFromNonUI();
+ }
+
+ @Override
+ public void onHostPause() {
+ stopFrameCallback();
+ }
+
+ @Override
+ public void onHostDestroy() {
+ stopFrameCallback();
+ }
+
+ public void onCatalystInstanceDestroyed() {
+ UiThreadUtil.runOnUiThread(
+ new Runnable() {
+ @Override
+ public void run() {
+ stopFrameCallback();
+ }
+ });
+ }
+
+ private void stopFrameCallback() {
+ UiThreadUtil.assertOnUiThread();
+ mCurrentFrameCallback.stop();
+ }
+
+ /**
+ * We use a staging data structure so that all UI events generated in a single frame are
+ * dispatched at once. Otherwise, a JS runnable enqueued in a previous frame could run while the
+ * UI thread is in the process of adding UI events and we might incorrectly send one event this
+ * frame and another from this frame during the next.
+ */
+ private void moveStagedEventsToDispatchQueue() {
+ synchronized (mEventsStagingLock) {
+ synchronized (mEventsToDispatchLock) {
+ for (int i = 0; i < mEventStaging.size(); i++) {
+ Event event = mEventStaging.get(i);
+
+ if (!event.canCoalesce()) {
+ addEventToEventsToDispatch(event);
+ continue;
+ }
+
+ long eventCookie =
+ getEventCookie(event.getViewTag(), event.getEventName(), event.getCoalescingKey());
+
+ Event eventToAdd = null;
+ Event eventToDispose = null;
+ Integer lastEventIdx = mEventCookieToLastEventIdx.get(eventCookie);
+
+ if (lastEventIdx == null) {
+ eventToAdd = event;
+ mEventCookieToLastEventIdx.put(eventCookie, mEventsToDispatchSize);
+ } else {
+ Event lastEvent = mEventsToDispatch[lastEventIdx];
+ Event coalescedEvent = event.coalesce(lastEvent);
+ if (coalescedEvent != lastEvent) {
+ eventToAdd = coalescedEvent;
+ mEventCookieToLastEventIdx.put(eventCookie, mEventsToDispatchSize);
+ eventToDispose = lastEvent;
+ mEventsToDispatch[lastEventIdx] = null;
+ } else {
+ eventToDispose = event;
+ }
+ }
+
+ if (eventToAdd != null) {
+ addEventToEventsToDispatch(eventToAdd);
+ }
+ if (eventToDispose != null) {
+ eventToDispose.dispose();
+ }
+ }
+ }
+ mEventStaging.clear();
+ }
+ }
+
+ private long getEventCookie(int viewTag, String eventName, short coalescingKey) {
+ short eventTypeId;
+ Short eventIdObj = mEventNameToEventId.get(eventName);
+ if (eventIdObj != null) {
+ eventTypeId = eventIdObj;
+ } else {
+ eventTypeId = mNextEventTypeId++;
+ mEventNameToEventId.put(eventName, eventTypeId);
+ }
+ return getEventCookie(viewTag, eventTypeId, coalescingKey);
+ }
+
+ private static long getEventCookie(int viewTag, short eventTypeId, short coalescingKey) {
+ return viewTag
+ | (((long) eventTypeId) & 0xffff) << 32
+ | (((long) coalescingKey) & 0xffff) << 48;
+ }
+
+ public void registerEventEmitter(@UIManagerType int uiManagerType, RCTEventEmitter eventEmitter) {
+ mReactEventEmitter.register(uiManagerType, eventEmitter);
+ }
+
+ public void unregisterEventEmitter(@UIManagerType int uiManagerType) {
+ mReactEventEmitter.unregister(uiManagerType);
+ }
+
+ private class ScheduleDispatchFrameCallback extends ChoreographerCompat.FrameCallback {
+ private volatile boolean mIsPosted = false;
+ private boolean mShouldStop = false;
+
+ @Override
+ public void doFrame(long frameTimeNanos) {
+ UiThreadUtil.assertOnUiThread();
+
+ if (mShouldStop) {
+ mIsPosted = false;
+ } else {
+ post();
+ }
+
+ Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "ScheduleDispatchFrameCallback");
+ try {
+ moveStagedEventsToDispatchQueue();
+
+ if (!mHasDispatchScheduled) {
+ mHasDispatchScheduled = true;
+ Systrace.startAsyncFlow(
+ Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
+ "ScheduleDispatchFrameCallback",
+ mHasDispatchScheduledCount.get());
+ mReactContext.runOnJSQueueThread(mDispatchEventsRunnable);
+ }
+ } finally {
+ Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
+ }
+ }
+
+ public void stop() {
+ mShouldStop = true;
+ }
+
+ public void maybePost() {
+ if (!mIsPosted) {
+ mIsPosted = true;
+ post();
+ }
+ }
+
+ private void post() {
+ ReactChoreographer.getInstance()
+ .postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, mCurrentFrameCallback);
+ }
+
+ public void maybePostFromNonUI() {
+ if (mIsPosted) {
+ return;
+ }
+
+ // We should only hit this slow path when we receive events while the host activity is paused.
+ if (mReactContext.isOnUiQueueThread()) {
+ maybePost();
+ } else {
+ mReactContext.runOnUiQueueThread(
+ new Runnable() {
+ @Override
+ public void run() {
+ maybePost();
+ }
+ });
+ }
+ }
+ }
+
+ private class DispatchEventsRunnable implements Runnable {
+
+ @Override
+ public void run() {
+ Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "DispatchEventsRunnable");
+ try {
+ Systrace.endAsyncFlow(
+ Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
+ "ScheduleDispatchFrameCallback",
+ mHasDispatchScheduledCount.getAndIncrement());
+ mHasDispatchScheduled = false;
+ Assertions.assertNotNull(mReactEventEmitter);
+ synchronized (mEventsToDispatchLock) {
+ if (mEventsToDispatchSize > 0) {
+ // We avoid allocating an array and iterator, and "sorting" if we don't need to.
+ // This occurs when the size of mEventsToDispatch is zero or one.
+ if (mEventsToDispatchSize > 1) {
+ Arrays.sort(mEventsToDispatch, 0, mEventsToDispatchSize, EVENT_COMPARATOR);
+ }
+ for (int eventIdx = 0; eventIdx < mEventsToDispatchSize; eventIdx++) {
+ Event event = mEventsToDispatch[eventIdx];
+ // Event can be null if it has been coalesced into another event.
+ if (event == null) {
+ continue;
+ }
+ Systrace.endAsyncFlow(
+ Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, event.getEventName(), event.getUniqueID());
+ event.dispatch(mReactEventEmitter);
+ event.dispose();
+ }
+ clearEventsToDispatch();
+ mEventCookieToLastEventIdx.clear();
+ }
+ }
+ for (BatchEventDispatchedListener listener : mPostEventDispatchListeners) {
+ listener.onBatchEventDispatched();
+ }
+ } finally {
+ Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
+ }
+ }
+ }
+
+ private void addEventToEventsToDispatch(Event event) {
+ if (mEventsToDispatchSize == mEventsToDispatch.length) {
+ mEventsToDispatch = Arrays.copyOf(mEventsToDispatch, 2 * mEventsToDispatch.length);
+ }
+ mEventsToDispatch[mEventsToDispatchSize++] = event;
+ }
+
+ private void clearEventsToDispatch() {
+ Arrays.fill(mEventsToDispatch, 0, mEventsToDispatchSize, null);
+ mEventsToDispatchSize = 0;
+ }
+}