Skip to content

Commit d2669f4

Browse files
capt-mujimeta-codesync[bot]
authored andcommitted
Android: make the batched-event-dispatch Choreographer frame callback one-shot (behind feature flag) (#58376)
Summary: # Make the batched-event-dispatch Choreographer frame callback one-shot (Android) Part of #58367. Split from #58368 per review feedback (one PR per pump, each behind a feature flag). `FabricEventDispatcher.ScheduleDispatchFrameCallback.doFrame` calls `dispatchBatchedEvents()` at the end of every frame — which (despite the name) only **re-posts the same frame callback**. Events themselves are dispatched synchronously as they are emitted, so once this callback is posted it keeps the `TIMERS_EVENTS` Choreographer armed at vsync rate forever, doing no work each frame beyond re-posting itself and firing the batch-dispatched listeners. This PR makes the callback one-shot, behind `ReactNativeFeatureFlags.disableIdleEventDispatchFrameCallbackRearmAndroid` (default **false** = current behavior; `ossReleaseStage: 'experimental'`): - With the flag on, `doFrame` clears `isFrameCallbackDispatchScheduled` and does not re-post. - Re-arming is already demand-driven in the existing code: `dispatchEvent` → `scheduleDispatchOfBatchedEvents` → `maybeDispatchBatchedEvents` posts the callback whenever new events arrive while it is not scheduled, and `onHostResume` re-posts. ## Foregrounding / backgrounding `onHostPause` / `onHostDestroy` → `cancelDispatchOfBatchedEvents()` and `onHostResume` → `scheduleDispatchOfBatchedEvents()` + `currentFrameCallback.resume()` are untouched. On the paused (`shouldStop`) frame the batch-dispatched listeners still fire, exactly as on `main`. ## Rollout Flag: `disableIdleEventDispatchFrameCallbackRearmAndroid` — `defaultValue: false`, `expectedReleaseValue: true`, `ossReleaseStage: 'experimental'`. With the flag off, behavior is identical to current `main`. Changelog: [Internal] Pull Request resolved: #58376 Test Plan: - [x] Flag off (`defaultValue`): branch structure is identical to `main` (the legacy `shouldStop`/re-post path is taken verbatim). - [x] Compiles: `./gradlew :packages:react-native:ReactAndroid:compileReleaseKotlin`. - [ ] Flag on, real app on device (OnePlus 3T, Android 9): idle `adb shell atrace` shows the event-dispatch `doFrame` stops firing between event bursts; touch/scroll event delivery unchanged; pause/resume keeps events flowing after foreground return. - [ ] Flag on: RNTester event integration tests pass. ## Changelog: [Android] [Fixed] - Make the batched-event-dispatch Choreographer frame callback one-shot instead of re-posting itself every frame (behind `disableIdleEventDispatchFrameCallbackRearmAndroid`) ## Prior art Same one-shot mechanism as #41658 (November 2023, removed by #43044 with the rest of `enableOnDemandReactChoreographer`). Comparison and analysis: #58367. Reviewed By: christophpurrer Differential Revision: D124365969 Pulled By: javache fbshipit-source-id: 553e6679c5f13b4f4cb8f1d1ac5aaac1759a22c3
1 parent c389859 commit d2669f4

23 files changed

Lines changed: 243 additions & 111 deletions

‎packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<72ad53973a7de132705b9ec1a72aac08>>
7+
* @generated SignedSource<<d824f6746b2cc7d4560164c04d123988>>
88
*/
99

1010
/**
@@ -60,6 +60,12 @@ public object ReactNativeFeatureFlags {
6060
@JvmStatic
6161
public fun disableEarlyViewCommandExecution(): Boolean = accessor.disableEarlyViewCommandExecution()
6262

63+
/**
64+
* Make the batched-event-dispatch Choreographer frame callback one-shot on Android instead of re-posting itself every frame
65+
*/
66+
@JvmStatic
67+
public fun disableIdleEventDispatchFrameCallbackRearmAndroid(): Boolean = accessor.disableIdleEventDispatchFrameCallbackRearmAndroid()
68+
6369
/**
6470
* Stop re-arming the DISPATCH_UI Choreographer frame callback at vsync rate while no mount items are pending on Android; queueing new items re-arms it
6571
*/

‎packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<60051ebbc387ff06db855ed661340217>>
7+
* @generated SignedSource<<ea771505a9f0b070dd8b2c4b50411c6e>>
88
*/
99

1010
/**
@@ -25,6 +25,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
2525
private var cxxNativeAnimatedEnabledCache: Boolean? = null
2626
private var defaultTextToOverflowHiddenCache: Boolean? = null
2727
private var disableEarlyViewCommandExecutionCache: Boolean? = null
28+
private var disableIdleEventDispatchFrameCallbackRearmAndroidCache: Boolean? = null
2829
private var disableIdleMountItemFrameCallbackRearmAndroidCache: Boolean? = null
2930
private var disableImageViewPreallocationAndroidCache: Boolean? = null
3031
private var disableMountItemReorderingAndroidCache: Boolean? = null
@@ -157,6 +158,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
157158
return cached
158159
}
159160

161+
override fun disableIdleEventDispatchFrameCallbackRearmAndroid(): Boolean {
162+
var cached = disableIdleEventDispatchFrameCallbackRearmAndroidCache
163+
if (cached == null) {
164+
cached = ReactNativeFeatureFlagsCxxInterop.disableIdleEventDispatchFrameCallbackRearmAndroid()
165+
disableIdleEventDispatchFrameCallbackRearmAndroidCache = cached
166+
}
167+
return cached
168+
}
169+
160170
override fun disableIdleMountItemFrameCallbackRearmAndroid(): Boolean {
161171
var cached = disableIdleMountItemFrameCallbackRearmAndroidCache
162172
if (cached == null) {

‎packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<9ed55c4c14d640c0fb2bd6c8fb7ef557>>
7+
* @generated SignedSource<<51b7baf7b3c2758ec619c77cb4fa637d>>
88
*/
99

1010
/**
@@ -38,6 +38,8 @@ public object ReactNativeFeatureFlagsCxxInterop {
3838

3939
@DoNotStrip @JvmStatic public external fun disableEarlyViewCommandExecution(): Boolean
4040

41+
@DoNotStrip @JvmStatic public external fun disableIdleEventDispatchFrameCallbackRearmAndroid(): Boolean
42+
4143
@DoNotStrip @JvmStatic public external fun disableIdleMountItemFrameCallbackRearmAndroid(): Boolean
4244

4345
@DoNotStrip @JvmStatic public external fun disableImageViewPreallocationAndroid(): Boolean

‎packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<2170527111c8b3c34c213c24a9948931>>
7+
* @generated SignedSource<<e8540d339cda9300ee0eac6e2ce316d2>>
88
*/
99

1010
/**
@@ -33,6 +33,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
3333

3434
override fun disableEarlyViewCommandExecution(): Boolean = false
3535

36+
override fun disableIdleEventDispatchFrameCallbackRearmAndroid(): Boolean = false
37+
3638
override fun disableIdleMountItemFrameCallbackRearmAndroid(): Boolean = false
3739

3840
override fun disableImageViewPreallocationAndroid(): Boolean = false

‎packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<a96a359df0616229d7a7979ae9499ecc>>
7+
* @generated SignedSource<<3e94c46a8a6fda2a4c0378e326291d73>>
88
*/
99

1010
/**
@@ -29,6 +29,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
2929
private var cxxNativeAnimatedEnabledCache: Boolean? = null
3030
private var defaultTextToOverflowHiddenCache: Boolean? = null
3131
private var disableEarlyViewCommandExecutionCache: Boolean? = null
32+
private var disableIdleEventDispatchFrameCallbackRearmAndroidCache: Boolean? = null
3233
private var disableIdleMountItemFrameCallbackRearmAndroidCache: Boolean? = null
3334
private var disableImageViewPreallocationAndroidCache: Boolean? = null
3435
private var disableMountItemReorderingAndroidCache: Boolean? = null
@@ -166,6 +167,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
166167
return cached
167168
}
168169

170+
override fun disableIdleEventDispatchFrameCallbackRearmAndroid(): Boolean {
171+
var cached = disableIdleEventDispatchFrameCallbackRearmAndroidCache
172+
if (cached == null) {
173+
cached = currentProvider.disableIdleEventDispatchFrameCallbackRearmAndroid()
174+
accessedFeatureFlags.add("disableIdleEventDispatchFrameCallbackRearmAndroid")
175+
disableIdleEventDispatchFrameCallbackRearmAndroidCache = cached
176+
}
177+
return cached
178+
}
179+
169180
override fun disableIdleMountItemFrameCallbackRearmAndroid(): Boolean {
170181
var cached = disableIdleMountItemFrameCallbackRearmAndroidCache
171182
if (cached == null) {

‎packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android.kt‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<9a51a403bfc02cd33bbe47c1f0fe6fd6>>
7+
* @generated SignedSource<<c5daa3cf1c88507ccb625ba7367dfcaf>>
88
*/
99

1010
/**
@@ -23,6 +23,8 @@ public open class ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android :
2323
// We could use JNI to get the defaults from C++,
2424
// but that is more expensive than just duplicating the defaults here.
2525

26+
override fun disableIdleEventDispatchFrameCallbackRearmAndroid(): Boolean = true
27+
2628
override fun disableIdleMountItemFrameCallbackRearmAndroid(): Boolean = true
2729

2830
override fun enableFlexboxAutoMinSizeInStrictMode(): Boolean = true

‎packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<256b97731c59551616a8e0cbc777f3fa>>
7+
* @generated SignedSource<<0c7d62679814835a3ef86a63174c7f7f>>
88
*/
99

1010
/**
@@ -33,6 +33,8 @@ public interface ReactNativeFeatureFlagsProvider {
3333

3434
@DoNotStrip public fun disableEarlyViewCommandExecution(): Boolean
3535

36+
@DoNotStrip public fun disableIdleEventDispatchFrameCallbackRearmAndroid(): Boolean
37+
3638
@DoNotStrip public fun disableIdleMountItemFrameCallbackRearmAndroid(): Boolean
3739

3840
@DoNotStrip public fun disableImageViewPreallocationAndroid(): Boolean

‎packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/FabricEventDispatcher.kt‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import com.facebook.react.bridge.ReactApplicationContext
1313
import com.facebook.react.bridge.ReactSoftExceptionLogger
1414
import com.facebook.react.bridge.UiThreadUtil
1515
import com.facebook.react.common.annotations.UnstableReactNativeAPI
16+
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
1617
import com.facebook.react.modules.core.ReactChoreographer
1718
import com.facebook.react.uimanager.UIManagerHelper
1819
import com.facebook.react.uimanager.common.UIManagerType
@@ -147,10 +148,18 @@ internal class FabricEventDispatcher(
147148
override fun doFrame(frameTimeNanos: Long) {
148149
UiThreadUtil.assertOnUiThread()
149150

150-
if (shouldStop) {
151-
isFrameCallbackDispatchScheduled = false
151+
if (!ReactNativeFeatureFlags.disableIdleEventDispatchFrameCallbackRearmAndroid()) {
152+
if (shouldStop) {
153+
isFrameCallbackDispatchScheduled = false
154+
} else {
155+
dispatchBatchedEvents()
156+
}
152157
} else {
153-
dispatchBatchedEvents()
158+
// One-shot semantics: events are dispatched synchronously as they are emitted, so a
159+
// posted callback only needs to fire the batch-dispatched listeners once. Re-posting
160+
// here kept the Choreographer armed at vsync rate while idle; new events re-post via
161+
// maybeDispatchBatchedEvents (see scheduleDispatchOfBatchedEvents/dispatchEvent).
162+
isFrameCallbackDispatchScheduled = false
154163
}
155164

156165
Systrace.beginSection(Systrace.TRACE_TAG_REACT, "BatchEventDispatchedListeners")

‎packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<2b4f896552dd54c3ec858ca788ab4254>>
7+
* @generated SignedSource<<b5f8c3c048123ddda3c712f289313b3f>>
88
*/
99

1010
/**
@@ -69,6 +69,12 @@ class ReactNativeFeatureFlagsJavaProvider
6969
return method(javaProvider_);
7070
}
7171

72+
bool disableIdleEventDispatchFrameCallbackRearmAndroid() override {
73+
static const auto method =
74+
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("disableIdleEventDispatchFrameCallbackRearmAndroid");
75+
return method(javaProvider_);
76+
}
77+
7278
bool disableIdleMountItemFrameCallbackRearmAndroid() override {
7379
static const auto method =
7480
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("disableIdleMountItemFrameCallbackRearmAndroid");
@@ -614,6 +620,11 @@ bool JReactNativeFeatureFlagsCxxInterop::disableEarlyViewCommandExecution(
614620
return ReactNativeFeatureFlags::disableEarlyViewCommandExecution();
615621
}
616622

623+
bool JReactNativeFeatureFlagsCxxInterop::disableIdleEventDispatchFrameCallbackRearmAndroid(
624+
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
625+
return ReactNativeFeatureFlags::disableIdleEventDispatchFrameCallbackRearmAndroid();
626+
}
627+
617628
bool JReactNativeFeatureFlagsCxxInterop::disableIdleMountItemFrameCallbackRearmAndroid(
618629
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
619630
return ReactNativeFeatureFlags::disableIdleMountItemFrameCallbackRearmAndroid();
@@ -1090,6 +1101,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
10901101
makeNativeMethod(
10911102
"disableEarlyViewCommandExecution",
10921103
JReactNativeFeatureFlagsCxxInterop::disableEarlyViewCommandExecution),
1104+
makeNativeMethod(
1105+
"disableIdleEventDispatchFrameCallbackRearmAndroid",
1106+
JReactNativeFeatureFlagsCxxInterop::disableIdleEventDispatchFrameCallbackRearmAndroid),
10931107
makeNativeMethod(
10941108
"disableIdleMountItemFrameCallbackRearmAndroid",
10951109
JReactNativeFeatureFlagsCxxInterop::disableIdleMountItemFrameCallbackRearmAndroid),

‎packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<228fff4f0ef0e530bf50203050adb9b0>>
7+
* @generated SignedSource<<a384c61ed84daad078fb102fdc734233>>
88
*/
99

1010
/**
@@ -45,6 +45,9 @@ class JReactNativeFeatureFlagsCxxInterop
4545
static bool disableEarlyViewCommandExecution(
4646
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
4747

48+
static bool disableIdleEventDispatchFrameCallbackRearmAndroid(
49+
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
50+
4851
static bool disableIdleMountItemFrameCallbackRearmAndroid(
4952
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
5053

0 commit comments

Comments
 (0)