Skip to content

Commit 7ee830d

Browse files
committed
Stop re-arming the idle NATIVE_ANIMATED_MODULE choreographer callback on Android
The animated frame callback re-posts itself unconditionally every frame, running at vsync rate while no animations are active. Behind disableIdleNativeAnimatedFrameCallbackRearmAndroid (default off): the callback arms only while animations run; didDispatchMountItems and the operation-enqueue helpers re-arm it when new animation operations arrive (covering imperative JS starts with no pending mount items).
1 parent 25c5539 commit 7ee830d

23 files changed

Lines changed: 252 additions & 106 deletions

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.kt

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import com.facebook.react.bridge.ScrollEndedListener
2121
import com.facebook.react.bridge.UIManager
2222
import com.facebook.react.bridge.UIManagerListener
2323
import com.facebook.react.bridge.buildReadableMap
24+
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
2425
import com.facebook.react.common.annotations.UnstableReactNativeAPI
2526
import com.facebook.react.common.annotations.VisibleForTesting
2627
import com.facebook.react.module.annotations.ReactModule
@@ -249,16 +250,28 @@ public class NativeAnimatedModule(reactContext: ReactApplicationContext) :
249250
private fun addOperation(operation: UIThreadOperation) {
250251
operation.batchNumber = currentBatchNumber
251252
operations.add(operation)
253+
armFrameCallbackForQueuedOperations()
252254
}
253255

254256
private fun addUnbatchedOperation(operation: UIThreadOperation) {
255257
operation.batchNumber = -1
256258
operations.add(operation)
259+
armFrameCallbackForQueuedOperations()
257260
}
258261

259262
private fun addPreOperation(operation: UIThreadOperation) {
260263
operation.batchNumber = currentBatchNumber
261264
preOperations.add(operation)
265+
armFrameCallbackForQueuedOperations()
266+
}
267+
268+
// With demand-gated re-arm, operations queued from the native module thread must re-arm the
269+
// frame callback themselves: they normally execute (and re-arm it) in didDispatchMountItems,
270+
// but imperative JS animation calls can arrive when no mount items are pending at all.
271+
private fun armFrameCallbackForQueuedOperations() {
272+
if (ReactNativeFeatureFlags.disableIdleNativeAnimatedFrameCallbackRearmAndroid()) {
273+
enqueueFrameCallback()
274+
}
262275
}
263276

264277
// For FabricUIManager only
@@ -299,6 +312,13 @@ public class NativeAnimatedModule(reactContext: ReactApplicationContext) :
299312

300313
preOperations.executeBatch(batchNumber, nodesManager)
301314
operations.executeBatch(batchNumber, nodesManager)
315+
316+
if (ReactNativeFeatureFlags.disableIdleNativeAnimatedFrameCallbackRearmAndroid()) {
317+
// Operations executed above may have started animations (e.g. startAnimatingNode); the
318+
// frame callback disarms itself when no animations are active, so re-arm it here.
319+
// didDispatchMountItems is UI-confined, like enqueueFrameCallback.
320+
enqueueFrameCallback()
321+
}
302322
}
303323

304324
// For non-FabricUIManager only (no-op since Fabric is the only supported UIManager)
@@ -339,7 +359,7 @@ public class NativeAnimatedModule(reactContext: ReactApplicationContext) :
339359
nodesManagerRef.set(nodesManager)
340360
}
341361

342-
private var enqueuedAnimationOnFrame = false
362+
@Volatile private var enqueuedAnimationOnFrame = false
343363
private val animatedFrameCallback =
344364
object : GuardedFrameCallback(reactContext) {
345365
override fun doFrameGuarded(frameTimeNanos: Long) {
@@ -348,9 +368,12 @@ public class NativeAnimatedModule(reactContext: ReactApplicationContext) :
348368
val nodesManager = nodesManager ?: return
349369
if (nodesManager.hasActiveAnimations()) {
350370
nodesManager.runUpdates(frameTimeNanos)
371+
enqueueFrameCallback()
372+
} else if (!ReactNativeFeatureFlags.disableIdleNativeAnimatedFrameCallbackRearmAndroid()) {
373+
// Only keep the Choreographer armed while animations are actually running; the
374+
// re-arm points above cover operations that may start new animations.
375+
enqueueFrameCallback()
351376
}
352-
353-
enqueueFrameCallback()
354377
} catch (ex: Exception) {
355378
throw RuntimeException(ex)
356379
}

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<<b7ef80c2c39c734ae511fe6457b89ce7>>
7+
* @generated SignedSource<<c409cd400d47962a7fc708b7b95540c6>>
88
*/
99

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

63+
/**
64+
* Stop re-arming the NATIVE_ANIMATED_MODULE Choreographer frame callback at vsync rate while no animations are active on Android
65+
*/
66+
@JvmStatic
67+
public fun disableIdleNativeAnimatedFrameCallbackRearmAndroid(): Boolean = accessor.disableIdleNativeAnimatedFrameCallbackRearmAndroid()
68+
6369
/**
6470
* Force disable view preallocation for images triggered from createNode off the main thread on Android
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<<68aefd0293540d56f57e8badc0de04c8>>
7+
* @generated SignedSource<<0d7c4d24c294b03b8e0b2d16e7f58ccc>>
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 disableIdleNativeAnimatedFrameCallbackRearmAndroidCache: Boolean? = null
2829
private var disableImageViewPreallocationAndroidCache: Boolean? = null
2930
private var disableMountItemReorderingAndroidCache: Boolean? = null
3031
private var disableSubviewClippingAndroidCache: Boolean? = null
@@ -152,6 +153,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
152153
return cached
153154
}
154155

156+
override fun disableIdleNativeAnimatedFrameCallbackRearmAndroid(): Boolean {
157+
var cached = disableIdleNativeAnimatedFrameCallbackRearmAndroidCache
158+
if (cached == null) {
159+
cached = ReactNativeFeatureFlagsCxxInterop.disableIdleNativeAnimatedFrameCallbackRearmAndroid()
160+
disableIdleNativeAnimatedFrameCallbackRearmAndroidCache = cached
161+
}
162+
return cached
163+
}
164+
155165
override fun disableImageViewPreallocationAndroid(): Boolean {
156166
var cached = disableImageViewPreallocationAndroidCache
157167
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<<247f721796621af8615014477518bcd9>>
7+
* @generated SignedSource<<2d29ddffdc0257599914ab6c55c52b40>>
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 disableIdleNativeAnimatedFrameCallbackRearmAndroid(): Boolean
42+
4143
@DoNotStrip @JvmStatic public external fun disableImageViewPreallocationAndroid(): Boolean
4244

4345
@DoNotStrip @JvmStatic public external fun disableMountItemReorderingAndroid(): 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<<33071257f9c96a8664c9af429e387061>>
7+
* @generated SignedSource<<42e8906f2b12d14277b3af082fc7595d>>
88
*/
99

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

3434
override fun disableEarlyViewCommandExecution(): Boolean = false
3535

36+
override fun disableIdleNativeAnimatedFrameCallbackRearmAndroid(): Boolean = false
37+
3638
override fun disableImageViewPreallocationAndroid(): Boolean = false
3739

3840
override fun disableMountItemReorderingAndroid(): 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<<f218220c66b8367211cae49adba46afc>>
7+
* @generated SignedSource<<37d43af00cb238b2e28108c2a98d6c8e>>
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 disableIdleNativeAnimatedFrameCallbackRearmAndroidCache: Boolean? = null
3233
private var disableImageViewPreallocationAndroidCache: Boolean? = null
3334
private var disableMountItemReorderingAndroidCache: Boolean? = null
3435
private var disableSubviewClippingAndroidCache: Boolean? = null
@@ -161,6 +162,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
161162
return cached
162163
}
163164

165+
override fun disableIdleNativeAnimatedFrameCallbackRearmAndroid(): Boolean {
166+
var cached = disableIdleNativeAnimatedFrameCallbackRearmAndroidCache
167+
if (cached == null) {
168+
cached = currentProvider.disableIdleNativeAnimatedFrameCallbackRearmAndroid()
169+
accessedFeatureFlags.add("disableIdleNativeAnimatedFrameCallbackRearmAndroid")
170+
disableIdleNativeAnimatedFrameCallbackRearmAndroidCache = cached
171+
}
172+
return cached
173+
}
174+
164175
override fun disableImageViewPreallocationAndroid(): Boolean {
165176
var cached = disableImageViewPreallocationAndroidCache
166177
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<<bad3dbaf92a0a869dfb91523ef4904f9>>
7+
* @generated SignedSource<<adde664babf1cfe0543c67229761825c>>
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 disableIdleNativeAnimatedFrameCallbackRearmAndroid(): Boolean = true
27+
2628
override fun enableFlexboxAutoMinSizeInStrictMode(): Boolean = true
2729

2830
override fun preventShadowTreeCommitExhaustion(): 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<<915bf918212b9898319de61d4cadaa13>>
7+
* @generated SignedSource<<e367592ef88f7bca939e0d148cfae64d>>
88
*/
99

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

3434
@DoNotStrip public fun disableEarlyViewCommandExecution(): Boolean
3535

36+
@DoNotStrip public fun disableIdleNativeAnimatedFrameCallbackRearmAndroid(): Boolean
37+
3638
@DoNotStrip public fun disableImageViewPreallocationAndroid(): Boolean
3739

3840
@DoNotStrip public fun disableMountItemReorderingAndroid(): Boolean

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<<177c5cc7f6e970a2d4454c32d7f777ef>>
7+
* @generated SignedSource<<ab7bac966684df1f1f46f798aa64c5c7>>
88
*/
99

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

72+
bool disableIdleNativeAnimatedFrameCallbackRearmAndroid() override {
73+
static const auto method =
74+
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("disableIdleNativeAnimatedFrameCallbackRearmAndroid");
75+
return method(javaProvider_);
76+
}
77+
7278
bool disableImageViewPreallocationAndroid() override {
7379
static const auto method =
7480
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("disableImageViewPreallocationAndroid");
@@ -584,6 +590,11 @@ bool JReactNativeFeatureFlagsCxxInterop::disableEarlyViewCommandExecution(
584590
return ReactNativeFeatureFlags::disableEarlyViewCommandExecution();
585591
}
586592

593+
bool JReactNativeFeatureFlagsCxxInterop::disableIdleNativeAnimatedFrameCallbackRearmAndroid(
594+
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
595+
return ReactNativeFeatureFlags::disableIdleNativeAnimatedFrameCallbackRearmAndroid();
596+
}
597+
587598
bool JReactNativeFeatureFlagsCxxInterop::disableImageViewPreallocationAndroid(
588599
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
589600
return ReactNativeFeatureFlags::disableImageViewPreallocationAndroid();
@@ -1035,6 +1046,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
10351046
makeNativeMethod(
10361047
"disableEarlyViewCommandExecution",
10371048
JReactNativeFeatureFlagsCxxInterop::disableEarlyViewCommandExecution),
1049+
makeNativeMethod(
1050+
"disableIdleNativeAnimatedFrameCallbackRearmAndroid",
1051+
JReactNativeFeatureFlagsCxxInterop::disableIdleNativeAnimatedFrameCallbackRearmAndroid),
10381052
makeNativeMethod(
10391053
"disableImageViewPreallocationAndroid",
10401054
JReactNativeFeatureFlagsCxxInterop::disableImageViewPreallocationAndroid),

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<<eab10e240a16c8ef3f659bd8ded290f0>>
7+
* @generated SignedSource<<1976b61d204740087d85a1c782602fed>>
88
*/
99

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

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

0 commit comments

Comments
 (0)